Sensores de posición

La plataforma de Android proporciona dos sensores que te permiten determinar la posición de un dispositivo: el sensor de campo geomagnético y el acelerómetro. La plataforma de Android también proporciona un sensor que te permite determinar qué tan cerca está la cara de un dispositivo de un objeto (conocido como sensor de proximidad). El sensor de campo geomagnético y el sensor de proximidad son basados en hardware. La mayoría de los fabricantes de teléfonos celulares y tablets incluyen un sensor de campo geomagnético. Del mismo modo, los fabricantes de teléfonos celulares suelen incluir un sensor de proximidad para determinar cuándo un teléfono celular se sostiene cerca del rostro de un usuario (por ejemplo, durante una llamada telefónica). Para determinar la orientación de un dispositivo, puedes usar las lecturas del acelerómetro y el sensor de campo geomagnético del dispositivo.

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

Los sensores de posición son útiles para determinar la posición física de un dispositivo en el marco de referencia del mundo. Por ejemplo, puedes usar el sensor de campo geomagnético en combinación con el acelerómetro para determinar la posición de un dispositivo en relación con el polo norte magnético. También puedes usar estos sensores para determinar la orientación de un dispositivo en el marco de referencia de tu aplicación. Por lo general, los sensores de posición no se usan para supervisar el movimiento del dispositivo, como sacudidas, inclinaciones o empujes (para obtener más información, consulta Sensores de movimiento).

El sensor de campo geomagnético y el acelerómetro muestran arreglos multidimensionales de valores de sensor para cada SensorEvent. Por ejemplo, el sensor de campo geomagnético proporciona valores de intensidad de campo geomagnético para cada uno de los tres ejes de coordenadas durante un solo evento del sensor. Del mismo modo, el sensor del acelerómetro mide la aceleración aplicada al dispositivo durante un evento del sensor. Para obtener más información sobre los sistemas de coordenadas que usan los sensores, consulta Sistemas de coordenadas de los sensores. El sensor de proximidad proporciona un solo valor para cada evento del sensor. En la tabla 1, se resumen los sensores de posición compatibles con la plataforma de Android.

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

Sensor Datos del evento del sensor Descripción Unidades de medición
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 del objeto.2 cm

1Este sensor dejó de estar disponible en Android 2.2 (nivel de API 8) y este tipo de sensor dejó de estar disponible en Android 4.4W (nivel de API 20). El framework de sensores proporciona métodos alternativos para adquirir la orientación del dispositivo, que se analizan en Cómo calcular la orientación del dispositivo.

2 Algunos sensores de proximidad solo proporcionan valores binarios que representan cerca y lejos.

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

El sensor de vector de rotación de juegos es idéntico al sensor de vector de rotación, excepto que no usa el campo geomagnético. Por lo tanto, el eje Y no apunta al norte, sino a alguna otra referencia. Esa referencia puede desviarse en el mismo orden de magnitud que el giroscopio alrededor del eje Z.

Dado que el sensor de vector de rotación del juego no usa el campo magnético, las rotaciones relativas son más precisas y no se ven afectadas por los cambios en el campo magnético. Usa este sensor en un juego si no te importa dónde está el norte y el vector de rotación normal no se ajusta a tus necesidades debido a su dependencia del campo magnético.

En el siguiente código, se muestra cómo obtener una instancia del sensor de vector de rotación del juego predeterminado:

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 de vector de rotación geomagnética es similar al sensor de vector de rotación, pero no usa el giroscopio. La precisión de este sensor es menor que la del sensor de vector de rotación normal, pero se reduce el consumo de energía. Usa este sensor solo si deseas recopilar información de rotación en segundo plano sin usar demasiada batería. Este sensor es más útil cuando se usa junto con el procesamiento por lotes.

En el siguiente código, se muestra cómo obtener una instancia del sensor de vector de rotación geomagnética 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 calcular la orientación del dispositivo

Cuando se calcula la orientación de un dispositivo, se puede supervisar su posición en relación con el marco de referencia de la Tierra (específicamente, el polo norte magnético). En el siguiente código, se muestra cómo calcular 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 calcula los ángulos de orientación con el sensor de campo geomagnético del dispositivo en combinación con el acelerómetro del dispositivo. Con estos dos sensores de hardware, el sistema proporciona datos para los siguientes tres ángulos de orientación:

  • Azimut (grados de rotación alrededor del eje -z). Es el ángulo entre la dirección de la brújula actual del dispositivo y el norte magnético. Si el borde superior del dispositivo está orientado al norte magnético, el azimut es de 0 grados. Si el borde superior está orientado al sur, el azimut es de 180 grados. Del mismo modo, si el borde superior está orientado al este, el azimut es de 90 grados, y si el borde superior está orientado al oeste, el azimut es de 270 grados.
  • Inclinación (grados de rotación alrededor del eje x). Es el ángulo entre un plano paralelo a la pantalla del dispositivo y un plano paralelo al suelo. Si sostienes el dispositivo en paralelo al suelo con el borde inferior más cercano a ti y inclinas el borde superior del dispositivo hacia el suelo, el ángulo de inclinación se vuelve positivo. Si se inclina en la dirección opuesta (se aleja el borde superior del dispositivo del suelo), el ángulo de inclinación se vuelve negativo. El rango de valores es de -90 grados a 90 grados.
  • Inclinación (grados de rotación alrededor del eje y). Es el ángulo entre un plano perpendicular a la pantalla del dispositivo y un plano perpendicular al suelo. Si sostienes el dispositivo en paralelo al suelo con el borde inferior más cercano a ti y lo inclinas hacia el suelo, el ángulo de balanceo se vuelve positivo. Si se inclina en la dirección opuesta (se mueve el borde derecho del dispositivo hacia el suelo), el ángulo de balanceo se vuelve negativo. El rango de valores es de -180 a 180 grados.

Nota:La definición de giro del sensor cambió para reflejar la gran mayoría de las implementaciones en el ecosistema de geosensores.

Ten en cuenta que estos ángulos funcionan con un sistema de coordenadas diferente al que se usa en la aviación (para el guiñada, el cabeceo y el balanceo). En el sistema de aviación, el eje x se encuentra a lo largo del lado largo del avión, de la cola a la nariz.

El sensor de orientación obtiene sus datos procesando los datos sin procesar del sensor del acelerómetro y del sensor de campo geomagnético. Debido al procesamiento intensivo que implica, se reduce la precisión del sensor de orientación. Específicamente, este sensor es confiable solo cuando el ángulo de balanceo es 0. Como resultado, el sensor de orientación dejó de estar disponible en Android 2.2 (nivel de API 8) y el tipo de sensor de orientación dejó de estar disponible en Android 4.4W (nivel de API 20). En lugar de usar datos sin procesar del sensor de orientación, te recomendamos que uses el método getRotationMatrix() junto con el método getOrientation() para calcular los valores de orientación, como se muestra en la siguiente muestra 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 tu 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
        )

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

        SensorManager.getOrientation(rotationMatrix, orientationAngles)

        // "orientationAngles" 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, magnetometerReading);

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

        SensorManager.getOrientation(rotationMatrix, orientationAngles);

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

Por lo general, no es necesario que realices ningún procesamiento ni filtrado de datos de los ángulos de orientación sin procesar del dispositivo, excepto traducir el sistema de coordenadas del sensor al marco de referencia de tu aplicación.

Cómo usar el sensor de campo geomagnético

El sensor de campo geomagnético te permite supervisar los cambios en el campo magnético de la Tierra. En el siguiente código, se 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);

Nota: Si tu app se segmenta a Android 12 (nivel de API 31) o versiones posteriores, este sensor tiene un límite de frecuencia.

Este sensor proporciona datos sin procesar de la intensidad del campo (en μT) para cada uno de los tres ejes de coordenadas. En general, no es necesario usar este sensor directamente. En su lugar, puedes usar el sensor de vector de rotación para determinar el movimiento de rotación sin procesar o 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. Luego, puedes usar estas matrices con los métodos getOrientation() y getInclination() para obtener datos de azimut y de inclinación geomagnética.

Nota: Cuando pruebes tu app, puedes mejorar la precisión del sensor agitando el dispositivo en un patrón de ocho.

Cómo usar el magnetómetro sin calibrar

El magnetómetro no calibrado es similar al sensor de campo geomagnético, excepto que no se aplica ninguna calibración de hierro duro al campo magnético. La calibración de fábrica y la compensación de temperatura aún se aplican al campo magnético. El magnetómetro no calibrado es útil para controlar las malas estimaciones de hierro duro. En general, geomagneticsensor_event.values[0] estará cerca de 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 resultados más sin procesar y pueden incluir algún sesgo, pero sus mediciones contienen menos saltos de las correcciones aplicadas a través de la calibración. Algunas aplicaciones pueden preferir estos resultados no calibrados, ya que son 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 no calibrado también proporciona la estimación del sesgo de hierro duro en cada eje. En el siguiente código, se muestra cómo obtener una instancia del magnetómetro predeterminado sin calibrar:

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. En el siguiente código, se 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);

Por lo general, el sensor de proximidad se usa para determinar qué tan lejos está la cabeza de una persona de la cara de un dispositivo de mano (por ejemplo, cuando un usuario realiza o recibe una llamada telefónica). La mayoría de los sensores de proximidad muestran la distancia absoluta, en cm, pero algunos solo muestran valores cercanos y lejanos.

Nota: En algunos modelos de dispositivos, el sensor de proximidad se encuentra debajo de la pantalla, lo que puede hacer que aparezca un punto intermitente en la pantalla si está habilitado mientras esta está encendida.

El siguiente código muestra cómo usar el sensor de proximidad:

Kotlin

class SensorActivity : Activity(), SensorEventListener {

    private lateinit var sensorManager: SensorManager
    private var proximity: 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
        proximity = 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()

        proximity?.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 muestran valores binarios que representan "cerca" o "lejos". En este caso, el sensor suele informar su valor de rango máximo en el estado lejano y un valor menor en el estado cercano. Por lo general, el valor lejano es un valor superior a 5 cm, pero puede variar de un sensor a otro. Puedes determinar el rango máximo de un sensor con el método getMaximumRange().

También debes leer: