Les capteurs

Introduction

La grande majorité des appareils mobiles disposent de capteurs (accéléromètres, gyroscopes, magnétomètres, …) dont il est possible d’utiliser via une application Android.

Les capteurs sous Android sont regroupés en plusieurs catégories :

  • les capteurs de mouvement (motion sensors) : ce sont principalement l’accéléromètre et le gyromètre.
  • les capteurs environnementaux : on peut trouver la température, la pression atmosphérique, l’éclairage et l’humidité.
  • les capteurs de position : on trouve l’accéléromètre, le magnétomètre et le détecteur de proximité.

Remarque : Tout capteur qui n’est pas un capteur de base est appelé capteur composite. Un capteur composite génère des données en traitant et/ou en fusionnant des données à partir d’un ou plusieurs capteurs physiques. Exemples de capteurs composites : détecteur de pas et mouvement significatif, vecteur de rotation (basé sur l’accéléromètre et le gyroscope), accélération linéaire (basée sur l’accéléromètre, le gyromètre et le magnétomètre)

Détecter les capteurs

Android fournit tout d’abord la classe SensorManager qui permet d’accèder aux différents capteurs.

Ensuite, on utilisera une instance de la classe Sensor pour utiliser un capteur individuellement.

Pour commencer, on va créer une simple application avec une fonction pour lister les capteurs disponibles de l’appareil mobile :

package com.example.myapplicationcapteurs;

import androidx.appcompat.app.AppCompatActivity;
import android.content.Context;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.Bundle;
import android.util.Log;
import android.widget.Toast;
import java.util.List;

public class MainActivity extends AppCompatActivity
{
    private final String TAG = "Capteurs";
    private SensorManager sensorManager = null;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listerCapteurs();
    }

    private boolean listerCapteurs()
    {
        List<Sensor> capteurs = sensorManager.getSensorList(Sensor.TYPE_ALL);

        if(capteurs != null && !capteurs.isEmpty())
        {
            for (Sensor capteur : capteurs)
            {
                Log.v(TAG, "capteur : " + capteur.toString());
            }
            return true;
        }
        else
        {
            Toast.makeText(getApplicationContext(), "Aucun capteur détecté !", Toast.LENGTH_SHORT).show();
        }
        return false;
    }
}

Voici ce que l’on obtient sur un téléphone LGE :

02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Accelerometer", vendor="InvenSense", version=1, type=1, maxRange=39.226593, resolution=0.0011901855, power=0.45, minDelay=8333}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Magnetic Field", vendor="ALPS", version=1, type=2, maxRange=2400.0, resolution=0.14953613, power=0.6, minDelay=16666}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Magnetic Field Uncalibrated", vendor="ALPS", version=1, type=14, maxRange=2400.0, resolution=0.14953613, power=0.6, minDelay=16666}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Gyroscope", vendor="InvenSense", version=1, type=4, maxRange=34.906586, resolution=0.0010681152, power=3.2, minDelay=5000}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Gyroscope Uncalibrated", vendor="InvenSense", version=1, type=16, maxRange=34.906586, resolution=0.0010681152, power=3.2, minDelay=5000}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Proximity", vendor="Avago", version=2, type=8, maxRange=5.0, resolution=5.0, power=12.675, minDelay=0}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Knock-on Proximity Sensor", vendor="LGE", version=1, type=499898103, maxRange=5.0, resolution=5.0, power=0.1, minDelay=0}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Virtual Proximity Sensor", vendor="LGE", version=1, type=499898105, maxRange=5.0, resolution=5.0, power=0.1, minDelay=0}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Light", vendor="Avago", version=2, type=5, maxRange=10000.0, resolution=0.009994507, power=0.175, minDelay=0}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Pressure", vendor="ALPS", version=1, type=6, maxRange=1100.0, resolution=0.013122559, power=1.5, minDelay=11111}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Gravity Sensor", vendor="QTI", version=1, type=9, maxRange=39.226593, resolution=0.0011901855, power=3.649994, minDelay=8333}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Linear Acceleration Sensor", vendor="QTI", version=1, type=10, maxRange=39.226593, resolution=0.0011901855, power=3.649994, minDelay=8333}
02-21 16:19:54.455 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Rotation Vector Sensor", vendor="QTI", version=1, type=11, maxRange=1.0, resolution=5.9604645E-8, power=4.2499847, minDelay=8333}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Step Detector Sensor", vendor="QTI", version=1, type=18, maxRange=1.0, resolution=1.0, power=0.44999695, minDelay=0}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Step Counter Sensor", vendor="QTI", version=1, type=19, maxRange=1.0, resolution=1.0, power=0.44999695, minDelay=0}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Significant Motion Detector Sensor", vendor="QTI", version=1, type=17, maxRange=1.0, resolution=1.0, power=0.44999695, minDelay=-1}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Game Rotation Vector Sensor", vendor="QTI", version=1, type=15, maxRange=1.0, resolution=5.9604645E-8, power=3.649994, minDelay=8333}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE GeoMagnetic Rotation Vector Sensor", vendor="QTI", version=1, type=20, maxRange=1.0, resolution=5.9604645E-8, power=1.0499878, minDelay=16666}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Orientation Sensor", vendor="QTI", version=1, type=3, maxRange=360.0, resolution=0.1, power=4.2499847, minDelay=8333}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Tilt Detector Sensor", vendor="QTI", version=1, type=22, maxRange=1.0, resolution=1.0, power=0.44999695, minDelay=0}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Absolute Motion Detector Sensor", vendor="QTI", version=1, type=33171006, maxRange=1.0, resolution=1.0, power=0.44999695, minDelay=0}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="LGE Relative Motion Detector Sensor", vendor="QTI", version=1, type=33171007, maxRange=1.0, resolution=1.0, power=0.44999695, minDelay=0}
02-21 16:19:54.456 21495-21495/com.example.myapplicationcapteurs V/Capteurs: capteur : {Sensor name="Motion Accel", vendor="QTI", version=1, type=499898101, maxRange=1.0, resolution=1.0, power=0.0, minDelay=0}

Acquérir un capteur

Il est possible de gérer une instance de la classe Sensor pour un type de capteur. Pour cela, on va appeler la méthode getDefaultSensor() de la classe SensorManager.

L’acquisition des données du capteur est réalisée par évènements. À chaque évènement, une donnée du capteur est associée avec le type de capteur, les valeurs brutes, la précision et l’horodatage (timestamp).

Pour cela, on va utiliser l’interface SensorEventListener. Il faudra alors implémenter deux méthodes de callback : onAccuracyChanged() lorsqu’il y a un changement de précision et surtout onSensorChanged() lorsqu’une nouvelle valeur est disponible. Pour cette dernière, on recevra en paramètre un SensorEvent qui contiendra les informations de précision (accuracy), la référence du capteur (sensor) , l’horodatage (timestamp) et évidemment les valeurs (values) dans un tableau.

Le principe est le suivant :

public class MainActivity extends AppCompatActivity implements SensorEventListener
{
    private final String TAG = "Capteurs";
    private SensorManager sensorManager = null;
    private Sensor accelerometre = null;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        detecterCapteurs();
    }

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

        if(sensorManager != null)
        {
            sensorManager.registerListener(this, accelerometre, SensorManager.SENSOR_DELAY_NORMAL); // = 200 ms ou SensorManager.SENSOR_DELAY_UI = 60 ms
        }
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        if(sensorManager != null)
        {
            sensorManager.unregisterListener(this);
        }
    }

    private void detecterCapteurs()
    {
        if(sensorManager == null)
        {
            sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        }

        boolean trouve = listerCapteurs();

        if(trouve)
        {
            accelerometre = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
            if(accelerometre != null)
                Log.v(TAG, "accéléromètre trouvé !");
        }
    }

    private boolean listerCapteurs()
    {
        List<Sensor> capteurs = sensorManager.getSensorList(Sensor.TYPE_ALL);

        if(capteurs != null && !capteurs.isEmpty())
        {
            for (Sensor capteur : capteurs)
            {
                Log.v(TAG, "capteur : " + capteur.toString());
            }
            return true;
        }
        else
        {
            Toast.makeText(getApplicationContext(), "Aucun capteur détecté !", Toast.LENGTH_SHORT).show();
        }
        return false;
    }

    @Override
    public void onSensorChanged(SensorEvent sensorEvent)
    {
        synchronized (this)
        {
            if(sensorEvent.sensor.getType() == Sensor.TYPE_ACCELEROMETER)
            {
                Log.v(TAG, "accéléromètre : " + "X " + sensorEvent.values[0] + " Y " + sensorEvent.values[1] + " Z " + sensorEvent.values[2]);
            }
        }
    }

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

On obtient dans Logcat :

...
02-21 16:48:47.234 21931-21931/com.example.myapplicationcapteurs V/Capteurs: accéléromètre : X -0.123672485 Y 0.03894043 Z 9.814529
02-21 16:48:47.432 21931-21931/com.example.myapplicationcapteurs V/Capteurs: accéléromètre : X -0.124923706 Y 0.04008484 Z 9.815857
...

Exemple : une boussole

On utilisera ici le capteur composite “Vecteur de rotation” :

public class MainActivity extends AppCompatActivity implements SensorEventListener
{
    private final String TAG = "Capteurs";
    private SensorManager sensorManager = null;
    private Sensor vecteurRotation = null;
    float [] orientation = new float[3];
    float [] matriceRotation = new float[9];
    private TextView texteDirection;

    @Override
    protected void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        texteDirection = (TextView)findViewById(R.id.texteDirection);
        texteDirection.setText("");

        detecterCapteurs();
    }

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

        if(sensorManager != null)
        {
            sensorManager.registerListener(this, vecteurRotation, SensorManager.SENSOR_DELAY_UI);
        }
    }

    @Override
    protected void onPause()
    {
        super.onPause();
        if(sensorManager != null)
        {
            sensorManager.unregisterListener(this);
        }
    }

    private void detecterCapteurs()
    {
        if(sensorManager == null)
        {
            sensorManager = (SensorManager)getSystemService(Context.SENSOR_SERVICE);
        }

        boolean trouve = listerCapteurs();

        if(trouve)
        {
            vecteurRotation = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
        }
    }

    private boolean listerCapteurs()
    {
        List<Sensor> capteurs = sensorManager.getSensorList(Sensor.TYPE_ALL);

        if(capteurs != null && !capteurs.isEmpty())
        {
            for (Sensor capteur : capteurs)
            {
                Log.v(TAG, "capteur : " + capteur.toString());
            }
            return true;
        }
        else
        {
            Toast.makeText(getApplicationContext(), "Aucun capteur détecté !", Toast.LENGTH_SHORT).show();
        }
        return false;
    }

    @Override
    public void onSensorChanged(SensorEvent sensorEvent)
    {
        synchronized (this)
        {            
            if(sensorEvent.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR)
            {
                // Calcul de la matrice de rotation
                SensorManager.getRotationMatrixFromVector(matriceRotation, sensorEvent.values);

                // Calcul de l'azimut
                double degres = (Math.toDegrees(SensorManager.getOrientation(matriceRotation, orientation)[0]) + 360) % 360;
                texteDirection.setText(String.format("%.2f", degres) + " °");
                Log.v(TAG, "azimuth : " + degres + " degrés");
            }
        }
    }

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

AndroidManifest.xml

Lorsque l’on utilise un capteur dans une application, il est préférable de le déclarer dans le fichier AndroidManifest.xml. Pour l’indiquer, il faut simplement ajouter une ligne <uses-feature> :

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.myapplicationcapteurs">

    <uses-feature
        android:name="android.hardware.sensor.accelerometer"
        android:required="true" />
    <uses-feature
        android:name="android.hardware.sensor.compass"
        android:required="true" />
    <uses-feature
        android:name="android.hardware.sensor.gyroscope"
        android:required="true" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">

        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

© Thierry Vaira <tvaira@free.fr>