Únete a ⁠ #Android11: The Beta Launch Show el 3 de junio.

Sensores de posición

La plataforma Android proporciona dos sensores que te permiten determinar la posición de un dispositivo: el sensor de campos geomagnéticos y el acelerómetro. En esta plataforma, también se encuentra un sensor (llamado sensor de proximidad) que permite determinar cuán cerca de un objeto se encuentra la cara de un dispositivo. El sensor de campos geomagnéticos y el sensor de proximidad se basan en hardware. La mayoría de los fabricantes de dispositivos portátiles y tablets incluyen un sensor de campos geomagnéticos. De la misma manera, los fabricantes de dispositivos portátiles a menudo incluyen un sensor de proximidad para determinar cuándo el usuario está sosteniendo un dispositivo portátil cerca de su rostro (por ejemplo, durante una llamada telefónica). Para determinar la orientación de un dispositivo, puedes usar las lecturas del acelerómetro del dispositivo y el sensor de campos geomagnéticos.

Nota: El sensor de orientación quedó obsoleto en Android 2.2 (API nivel 8) y el tipo de sensor de orientación quedó obsoleto en Android 4.4W (API nivel 20).

Los sensores de posición son útiles para determinar la posición física de un dispositivo en el marco de referencia mundial. Por ejemplo, puedes usar el sensor de campos geomagnéticos junto con el acelerómetro para determinar la posición de un dispositivo relativa al polo norte magnético. También puedes usar estos sensores para determinar la orientación de un dispositivo en el marco de referencia de la aplicación. Los sensores de posición no suelen usarse para supervisar el movimiento del dispositivo, por ejemplo, si se sacude, se inclina o se empuja (para obtener más información, consulta Sensores de movimiento).

El sensor de campos geomagnéticos y el acelerómetro devuelven matrices multidimensionales de valores del sensor para cada SensorEvent. Por ejemplo, el sensor de campos geomagnéticos proporciona valores de fuerza de dicho campo para cada uno de los tres ejes de coordenadas durante un evento de sensor único. De la misma manera, el sensor acelerómetro mide la aceleración que se aplica a un dispositivo durante un evento de sensor. Para obtener más información sobre los sistemas de coordenadas que utilizan los sensores, consulta Sistemas de coordenadas de sensores. El sensor de proximidad proporciona un único valor para cada evento de sensor. En la tabla 1, se resumen los sensores de posición compatibles con la plataforma Android.

Tabla 1. Sensores de posición compatibles con la plataforma Android

Sensor Datos de eventos de los sensores Descripción Unidades de medida
TYPE_GAME_ROTATION_VECTOR SensorEvent.values[0] Indica el componente vectorial de rotación junto al eje x (x * sin(θ/2)). Sin unidades
SensorEvent.values[1] Indica el componente vectorial de rotación junto al eje y (y * sin(θ/2)).
SensorEvent.values[2] Indica el componente vectorial de rotación junto al eje z (z * sin(θ/2)).
TYPE_GEOMAGNETIC_ROTATION_VECTOR SensorEvent.values[0] Indica el componente vectorial de rotación junto al eje x (x * sin(θ/2)). Sin unidades
SensorEvent.values[1] Indica el componente vectorial de rotación junto al eje y (y * sin(θ/2)).
SensorEvent.values[2] Indica el componente vectorial de rotación junto al eje z (z * sin(θ/2)).
TYPE_MAGNETIC_FIELD SensorEvent.values[0] Fuerza del campo geomagnético junto al eje x. μT
SensorEvent.values[1] Fuerza del campo geomagnético junto al eje y.
SensorEvent.values[2] Fuerza del campo geomagnético junto al eje z.
TYPE_MAGNETIC_FIELD_UNCALIBRATED SensorEvent.values[0] Fuerza del campo geomagnético (sin calibración de hierro resistente) junto al eje x. μT
SensorEvent.values[1] Fuerza del campo geomagnético (sin calibración de hierro resistente) junto al eje y.
SensorEvent.values[2] Fuerza del campo geomagnético (sin calibración de hierro resistente) junto al eje z.
SensorEvent.values[3] Estimación del sesgo de hierro junto al eje x.
SensorEvent.values[4] Estimación del sesgo de hierro junto al eje y.
SensorEvent.values[5] Estimación del sesgo de hierro junto al eje z.
TYPE_ORIENTATION1 SensorEvent.values[0] Azimuth (ángulo en torno al eje z). Grados
SensorEvent.values[1] Pitch (ángulo en torno al eje x).
SensorEvent.values[2] Roll (ángulo en torno al eje y).
TYPE_PROXIMITY SensorEvent.values[0] Distancia respecto del objeto.2 cm

1Este sensor quedó obsoleto en Android 2.2 (API nivel 8) y este tipo de sensor quedó obsoleto en Android 4.4W (API nivel 20). El marco de trabajo del sensor proporciona métodos alternativos para conseguir la orientación del dispositivo, que se analizan en Cómo computar la orientación del dispositivo.

2 Algunos sensores de proximidad solo proporcionan valores binarios que representan la cercanía y la lejanía.

Cómo usar el sensor del vector de rotación del juego

El sensor del vector de rotación del juego es idéntico al sensor del vector de rotación, excepto que no utiliza el campo geomagnético. Por lo tanto, el eje Y no apunta al norte, sino a otra referencia. A la referencia se le permite desviarse con el mismo orden de magnitud que las desviaciones del giroscopio en torno al eje Z.

Dado que el sensor del vector de rotación del juego no utiliza el campo magnético, las rotaciones relativas son más precisas y no se ven afectadas por los cambios en el campo magnético. Puedes utilizar este sensor en un juego si no interesa dónde se encuentra el norte y el vector de rotación normal no se ajusta a tus necesidades porque depende del campo magnético.

El siguiente código muestra cómo obtener una instancia del sensor del vector de rotación predeterminado del juego:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GAME_ROTATION_VECTOR);

Cómo usar el sensor del vector de rotación geomagnético

El sensor del vector de rotación geomagnético es similar al sensor del vector de rotación, pero utiliza un magnetómetro en lugar de un giroscopio. La precisión de este sensor es inferior a la del sensor del vector de rotación normal, pero el consumo energético se ve reducido. Solo debes usar este sensor si deseas recopilar información de rotación en segundo plano sin gastar demasiada batería. Este sensor resulta más útil cuando se usa junto con el procesamiento por lotes.

El siguiente código muestra cómo obtener una instancia del sensor del vector de rotación geomagnético predeterminado:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR);

Cómo computar la orientación del dispositivo

Al computar la orientación de un dispositivo, puedes supervisar la orientación del dispositivo relativa al marco de referencia de la Tierra (específicamente, el polo norte magnético). El siguiente código muestra cómo computar la orientación de un dispositivo:

Kotlin

private lateinit var sensorManager: SensorManager
...
// Rotation matrix based on current readings from accelerometer and magnetometer.
val rotationMatrix = FloatArray(9)
SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerReading, magnetometerReading)

// Express the updated rotation matrix as three orientation angles.
val orientationAngles = FloatArray(3)
SensorManager.getOrientation(rotationMatrix, orientationAngles)

Java

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

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

El sistema computa los ángulos de orientación utilizando el sensor de campos geomagnéticos de un dispositivo junto con el acelerómetro del dispositivo. Cuando se usan estos dos sensores de hardware, el sistema proporciona datos para los siguientes tres ángulos de orientación:

  • Azimuth (grados de rotación sobre el eje z). Este es el ángulo entre la dirección del compás actual del dispositivo y el norte magnético. Si el borde superior del dispositivo apunta al norte magnético, el valor azimuth es de 0 grados; si el borde superior apunta al sur, el valor azimuth es de 180 grados. De igual modo, si el borde superior apunta al este, el valor azimuth es de 90 grados; en cambio, si apunta al oeste, el valor azimuth es de 270 grados.
  • Pitch (grados de rotación sobre el eje x). Este es el ángulo entre un plano paralelo a la pantalla del dispositivo y un plano paralelo al suelo. Si sostienes el dispositivo paralelo al suelo con el borde inferior lo más cerca posible de tu cuerpo e inclinas el borde superior del dispositivo hacia el suelo, el valor del ángulo de orientación pitch se convierte en positivo. La inclinación en sentido opuesto, es decir, alejando el borde superior del dispositivo respecto del suelo, provoca que el ángulo pitch se convierta en negativo. El rango de valores va de -180 grados a 180 grados.
  • Roll (grados de rotación en torno al eje y). Este es el ángulo entre un plano perpendicular a la pantalla del dispositivo y un plano perpendicular al suelo. Si sostienes el dispositivo paralelo al suelo con el borde inferior lo más cerca posible de tu cuerpo e inclinas el borde izquierdo del dispositivo hacia el suelo, el valor del ángulo de orientación roll se convierte en positivo. La inclinación en sentido opuesto, es decir, acerando el borde derecho del dispositivo hacia el suelo, provoca que el ángulo roll se convierta en negativo. El rango de valores va de -90 grados a 90 grados.

Nota:La definición de ángulo roll del sensor se modificó para que refleje la amplia mayoría de implementaciones en el ecosistema del geosensor.

Ten en cuenta que estos ángulos trabajan a partir de un sistema de coordenadas diferente que el utilizado para la aviación (para orientaciones yaw, pitch y roll). En el sistema de aviación, el eje x se encuentra junto al lado largo del plano, desde la cola hasta la punta.

Para derivar sus datos, el sensor de orientación procesa los datos del sensor sin procesar obtenidos del acelerómetro y del sensor de campos geomagnéticos. Dado que hay una gran carga de procesamiento, la precisión y la exactitud del sensor de orientación se ven reducidas. Específicamente, este sensor solo es confiable cuando el ángulo roll es 0. Como consecuencia, el sensor de orientación quedó obsoleto en Android 2.2 (API nivel 8) y el tipo de sensor de orientación quedó obsoleto en Android 4.4W (API nivel 20). En lugar de utilizar datos sin procesar obtenidos del sensor de orientación, recomendamos que utilices el método getRotationMatrix() junto con el método getOrientation() para computar los valores de orientación, tal como se muestra en el siguiente ejemplo de código. Como parte de este proceso, puedes usar el método remapCoordinateSystem() para traducir los valores de orientación al marco de referencia de la aplicación.

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private val accelerometerReading = FloatArray(3)
    private val magnetometerReading = FloatArray(3)

    private val rotationMatrix = FloatArray(9)
    private val orientationAngles = FloatArray(3)

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
        // You must implement this callback in your code.
    }

    override fun onResume() {
        super.onResume()

        // Get updates from the accelerometer and magnetometer at a constant rate.
        // To make batch operations more efficient and reduce power consumption,
        // provide support for delaying updates to the application.
        //
        // In this example, the sensor reporting delay is small enough such that
        // the application receives an update before the system checks the sensor
        // readings again.
        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER)?.also { accelerometer ->
            sensorManager.registerListener(
                    this,
                    accelerometer,
                    SensorManager.SENSOR_DELAY_NORMAL,
                    SensorManager.SENSOR_DELAY_UI
            )
        }
        sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)?.also { magneticField ->
            sensorManager.registerListener(
                    this,
                    magneticField,
                    SensorManager.SENSOR_DELAY_NORMAL,
                    SensorManager.SENSOR_DELAY_UI
            )
        }
    }

    override fun onPause() {
        super.onPause()

        // Don't receive any more updates from either sensor.
        sensorManager.unregisterListener(this)
    }

    // Get readings from accelerometer and magnetometer. To simplify calculations,
    // consider storing these readings as unit vectors.
    override fun onSensorChanged(event: SensorEvent) {
        if (event.sensor.type == Sensor.TYPE_ACCELEROMETER) {
            System.arraycopy(event.values, 0, accelerometerReading, 0, accelerometerReading.size)
        } else if (event.sensor.type == Sensor.TYPE_MAGNETIC_FIELD) {
            System.arraycopy(event.values, 0, magnetometerReading, 0, magnetometerReading.size)
        }
    }

    // Compute the three orientation angles based on the most recent readings from
    // the device's accelerometer and magnetometer.
    fun updateOrientationAngles() {
        // Update rotation matrix, which is needed to update orientation angles.
        SensorManager.getRotationMatrix(
                rotationMatrix,
                null,
                accelerometerReading,
                magnetometerReading
        )

        // "mRotationMatrix" now has up-to-date information.

        SensorManager.getOrientation(rotationMatrix, mOrientationAngles)

        // "mOrientationAngles" now has up-to-date information.
    }
}

Java

public class SensorActivity extends Activity implements SensorEventListener {

    private SensorManager sensorManager;
    private final float[] accelerometerReading = new float[3];
    private final float[] magnetometerReading = new float[3];

    private final float[] rotationMatrix = new float[9];
    private final float[] orientationAngles = new float[3];

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes.
        // You must implement this callback in your code.
    }

    @Override
    protected void onResume() {
        super.onResume();

        // Get updates from the accelerometer and magnetometer at a constant rate.
        // To make batch operations more efficient and reduce power consumption,
        // provide support for delaying updates to the application.
        //
        // In this example, the sensor reporting delay is small enough such that
        // the application receives an update before the system checks the sensor
        // readings again.
        Sensor accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
        if (accelerometer != null) {
            sensorManager.registerListener(this, accelerometer,
                SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
        }
        Sensor magneticField = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
        if (magneticField != null) {
            sensorManager.registerListener(this, magneticField,
                SensorManager.SENSOR_DELAY_NORMAL, SensorManager.SENSOR_DELAY_UI);
        }
    }

    @Override
    protected void onPause() {
        super.onPause();

        // Don't receive any more updates from either sensor.
        sensorManager.unregisterListener(this);
    }

    // Get readings from accelerometer and magnetometer. To simplify calculations,
    // consider storing these readings as unit vectors.
    @Override
    public void onSensorChanged(SensorEvent event) {
        if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {
          System.arraycopy(event.values, 0, accelerometerReading,
              0, accelerometerReading.length);
        } else if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
            System.arraycopy(event.values, 0, magnetometerReading,
                0, magnetometerReading.length);
        }
    }

    // Compute the three orientation angles based on the most recent readings from
    // the device's accelerometer and magnetometer.
    public void updateOrientationAngles() {
        // Update rotation matrix, which is needed to update orientation angles.
        SensorManager.getRotationMatrix(rotationMatrix, null,
            accelerometerReading, mMagnetometerReading);

        // "mRotationMatrix" now has up-to-date information.

        SensorManager.getOrientation(rotationMatrix, mOrientationAngles);

        // "mOrientationAngles" now has up-to-date information.
    }
}

En general, no es necesario procesar ni filtrar datos sin procesar sobre los ángulos de orientación del dispositivo; solo necesitas traducir el sistema de coordenadas del sensor al marco de referencia de la aplicación.

Cómo usar el sensor de campo geomagnético

El sensor de campo geomagnético permite supervisar los cambios que se producen en el campo magnético de la Tierra. El siguiente código muestra cómo obtener una instancia del sensor de campo geomagnético predeterminado:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

Este sensor proporciona datos sin procesar sobre la fuerza del campo (en μT) para cada uno de los tres ejes de coordenadas. En general, no es necesario usar este sensor directamente. En cambio, puedes usar el sensor del vector de rotación para determinar el movimiento rotatorio sin procesar, o bien puedes usar el acelerómetro y el sensor de campo geomagnético junto con el método getRotationMatrix() para obtener la matriz de rotación y la matriz de inclinación. A continuación, puedes usar estas matrices con los métodos getOrientation() y getInclination() para obtener los datos de azimuth e inclinación geomagnética.

Nota: Al probar la app, puedes mejorar la precisión del sensor si mueves el dispositivo formando un 8 horizontal en el aire.

Cómo usar el magnetómetro sin calibrar

El magnetómetro sin calibrar es parecido al sensor de campo geomagnético, excepto que al campo magnético no se le aplica calibración de hierro resistente. Sí se le aplican la calibración de fábrica y la compensación de temperatura. El magnetómetro sin calibrar es útil para controlar las estimaciones defectuosas de hierro resistente. En general, geomagneticsensor_event.values[0] se aproxima a uncalibrated_magnetometer_event.values[0] - uncalibrated_magnetometer_event.values[3]. Es decir:

calibrated_x ~= uncalibrated_x - bias_estimate_x

Nota: Los sensores no calibrados proporcionan más resultados sin procesar y pueden incluir algún sesgo, aunque sus mediciones contienen menos saltos de correcciones aplicadas a través de la calibración. Es posible que en algunas aplicaciones se prefieran estos resultados sin calibrar como más fluidos y confiables. Por ejemplo, si una aplicación intenta realizar su propia fusión de sensores, introducir calibraciones puede distorsionar los resultados.

Además del campo magnético, el magnetómetro sin calibrar también proporciona el sesgo de hierro resistente estimado para cada eje. El siguiente código muestra cómo obtener una instancia del magnetómetro sin calibrar predeterminado:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED);

Cómo usar el sensor de proximidad

El sensor de proximidad permite determinar a qué distancia se encuentra un objeto respecto de un dispositivo. El siguiente código muestra cómo obtener una instancia del sensor de proximidad predeterminado:

Kotlin

private lateinit var sensorManager: SensorManager
private var sensor: Sensor? = null
...
sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)

Java

private SensorManager sensorManager;
private Sensor sensor;
...
sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
sensor = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

El sensor de proximidad a menudo se utiliza para determinar a qué distancia se encuentra la cabeza de una persona respecto de la cara de un dispositivo móvil (por ejemplo, cuando un usuario realiza o recibe una llamada telefónica). La mayoría de los sensores de proximidad informan la distancia absoluta en centímetros, pero algunos informan solo valores de cercanía y lejanía. El siguiente código muestra cómo usar el sensor de proximidad:

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private var mProximity: Sensor? = null

    public override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.main)

        // Get an instance of the sensor service, and use that to get an instance of
        // a particular sensor.
        sensorManager = getSystemService(Context.SENSOR_SERVICE) as SensorManager
        mProximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY)
    }

    override fun onAccuracyChanged(sensor: Sensor, accuracy: Int) {
        // Do something here if sensor accuracy changes.
    }

    override fun onSensorChanged(event: SensorEvent) {
        val distance = event.values[0]
        // Do something with this sensor data.
    }

    override fun onResume() {
        // Register a listener for the sensor.
        super.onResume()

        mProximity?.also { proximity ->
            sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL)
        }
    }

    override fun onPause() {
        // Be sure to unregister the sensor when the activity pauses.
        super.onPause()
        sensorManager.unregisterListener(this)
    }
}

Java

public class SensorActivity extends Activity implements SensorEventListener {
    private SensorManager sensorManager;
    private Sensor proximity;

    @Override
    public final void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        // Get an instance of the sensor service, and use that to get an instance of
        // a particular sensor.
        sensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);
    }

    @Override
    public final void onAccuracyChanged(Sensor sensor, int accuracy) {
        // Do something here if sensor accuracy changes.
    }

    @Override
    public final void onSensorChanged(SensorEvent event) {
        float distance = event.values[0];
        // Do something with this sensor data.
    }

    @Override
    protected void onResume() {
        // Register a listener for the sensor.
        super.onResume();
        sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_NORMAL);
      }

    @Override
    protected void onPause() {
        // Be sure to unregister the sensor when the activity pauses.
        super.onPause();
        sensorManager.unregisterListener(this);
    }
}

Nota: Algunos sensores de proximidad informan valores binarios que indican si el objeto se encuentra "cerca" o "lejos". En este caso, el sensor a menudo informa el valor de rango máximo en cuanto a lejanía y un valor menor para cercanía. Generalmente, el valor de lejanía es > 5 cm, pero puede variar según el sensor. Puedes usar el método getMaximumRange() para determinar el rango máximo de un sensor.

También debes leer: