• 30 heures
  • Moyenne

Ce cours est visible gratuitement en ligne.

course.header.alt.is_video

course.header.alt.is_certifying

J'ai tout compris !

Mis à jour le 27/03/2018

Utilisez des capteurs dans vos applications

Connectez-vous ou inscrivez-vous gratuitement pour bénéficier de toutes les fonctionnalités de ce cours !

On peut réunir les différents types de capteurs en 4 familles  :

  • environnementaux (T°, luminosité, ...) ;

  • de position (boussole, géolocalisation, ...) ;

  • de mouvement (Accéléromètre, gyroscope, ...) ;

  • corporels (Rythme cardiaque).

On y accède par une unique bibliothèque SensorManager, à l'exception du cas de la géolocalisation. Dans ce chapitre, nous ne traiterons que du cas général. Le cas spécifique de la géolocalisation sera traité dans le chapitre suivant.

Des capteurs ? Mais concrètement, de quoi parle-t-on ?

Avant d'aller plus loin, pour que vous compreniez bien de quoi on parle, voici la liste des capteurs référencés dans l'API 26 (Android 8) :

  • TYPE_ACCELEROMETER

  • TYPE_AMBIENT_TEMPERATURE

  • TYPE_DEVICE_PRIVATE_BASE

  • TYPE_GAME_ROTATION_VECTOR

  • TYPE_GEOMAGNETIC_ROTATION_VECTOR

  • TYPE_GRAVITY

  • TYPE_GYROSCOPE

  • TYPE_HEART_BEAT

  • TYPE_HEART_RATE

  • TYPE_LIGHT

  • TYPE_LINEAR_ACCELERATION

  • TYPE_LOW_LATENCY_OFFBODY_DETECT

  • TYPE_MAGNETIC_FIELD

  • TYPE_MOTION_DETECT

  • TYPE_ORIENTATION

  • TYPE_POSE_6DOF

  • TYPE_PRESSURE

  • TYPE_PROXIMITY

  • TYPE_RELATIVE_HUMIDITY

  • TYPE_ROTATION_VECTOR

  • TYPE_SIGNIFICANT_MOTION

  • TYPE_STATIONARY_DETECT

  • TYPE_STEP_COUNTER

  • TYPE_STEP_DETECTOR

  • TYPE_TEMPERATURE

Tous ces capteurs ne sont pas toujours disponibles sur tous les appareils. D'ailleurs, certains capteurs sont physiques, tandis que d'autres sont "virtuels". C'est-à-dire qu'ils sont calculés à partir des données d'autres capteurs.

Parmi les capteurs les plus courants, notons l'accéléromètre. C'est un accéléromètre linéaire. Il est beaucoup utilisé, car c'est lui qui permet de connaître le plus facilement l'orientation de l'écran et qui permettra de faire une rotation automatique de l'affichage.

Un accéléromètre linéaire pour détecter une rotation ??? J'ai raté un truc :o ?

Petit rappel de physique de quand nous étions petits : la gravité est une accélération linéaire qui nous tire vers le sol. Sa valeur est de l'ordre de  $\(9,81 m.s^{-2}\)$ . Ainsi, mesurer l'accélération linéaire permet de retrouver l'information du sens de la gravité.

Ce capteur TYPE_ACCELEROMETER  est également accessible de manière filtrée autour de la gravité : le capteur TYPE_GRAVITY  est l'accéléromètre linéaire filtré pour ne montrer que la gravité, tandis queTYPE_LINEAR_ACCELERATION  est le même capteur, mais filtré pour ne pas avoir la gravité. Globalement, la relation suivante relie les 3 capteurs :

$\[acceleration = gravity + linear\_acceleration\]$

Un autre capteur courant est le gyroscope. Il fournit une vitesse de rotation en radian par seconde. Il est utilisé dans un certain nombre d'applications basées sur la réalité augmentée, comme Pokemon Go.

Il y a également très souvent un capteur de type TYPE_LIGHT  sur les Android, qui renvoie une intensité lumineuse en lux. Il sera principalement utilisé pour gérer automatiquement l'intensité lumineuse de l'écran. Ce capteur est un exemple de capteur à une dimension, contrairement aux capteurs de mouvement qui sont souvent à 3 dimensions.

Enfin, parmi les capteurs à 1 dimension très courants sur les téléphones, il y a TYPE_PROXIMITY, qui permet de savoir si quelque chose est proche de l'écran. Cela permet de désactiver l'écran lorsqu'une oreille est contre l'écouteur pendant un appel téléphonique ;). Ce capteur renvoie une information en centimètres, mais la plupart du temps, il ne renvoie que 2 valeurs possibles, qui sont assimilées à près et loin.

Nous ne prendrons pas le temps de décrire tous les capteurs, mais n'hésitez pas à lire la documentation officielle pour en savoir plus :

https://developer.android.com/reference/android/hardware/Sensor.html

Principe général

Quel que soit le type de capteur que l'on veut utiliser, le principe reste le même : on s'inscrit à un service et on reçoit des notifications lorsqu'une nouvelle valeur est disponible. Comme pour les boutons, c'est de la programmation événementielle. Donc, dans les grandes lignes, exploiter des données reçues d'un capteur se résume à :

  1. écrire un écouteur ;

  2. enregistrer l'écouteur auprès du SensorManager  pour une sonde donnée.

Et c'est tout ! :D

Voyons maintenant plus en détail le principe d'accès à un capteur.

Écrire un écouteur

Un écouteur prévu pour recevoir des informations venant de capteurs est une classe devant implémenter l'interface SensorEventListener. Cette interface est très légère puisqu'elle se réduit à 2 méthodes :

public interface SensorEventListener {
    public void onSensorChanged(SensorEvent event) ;
    /*  cette méthode sera appelée par le système à chaque fois qu'une nouvelle 
        valeur sera disponible. Le paramètre contient la description de l'événement. 
        Les 2 choses les plus importantes présentes dans cette description sont
        la nouvelle valeur du capteur et la nature du capteur :
             * event.values
             * event.sensor.getType()
    */

    public void onAccuracyChanged(Sensor sensor, int accuracy) ;
    /* cette méthode sera appelée par le système à chaque fois que la précision de la 
       mesure change (SENSOR_STATUS_ACCURACY_{UNRELIABLE, LOW, MEDIUM, HIGH}). 
       Concrètement, la plupart du temps, cette méthode est vide.
    */  
}

Parmi les points à ne pas rater, notez que la plupart des capteurs sont multi-dimensionnels (par exemple, un accéléromètre aura une accélération dans un espace à 3 dimensions). Donc la valeur reçue dans event  est un tableau, dont la taille peut varier. Bien que certains capteurs n'aient qu'une seule valeur (ex : capteur de luminosité), la pratique montre que ce tableau dispose généralement d'au moins 3 cases.

Enregistrer l'écouteur auprès du SensorManager pour une sonde particulière

La manière la plus courante d'enregistrer un écouteur auprès du système pour une sonde particulière suit le schéma suivant :

private SensorManager sensorManager;
@Override
protected void onResume() {
    super.onResume();

    sensorManager =  (SensorManager) getSystemService(SENSOR_SERVICE);
    sensorManager.registerListener(
        <mon écouteur>, 
        sensorManager.getDefaultSensor(<le capteur voulu>), 
        <le taux de raffraichissement voulu>);
}

@Override
protected void onPause() {
    super.onPause();
    sensorManager.unregisterListener(<mon écouteur>);
}

La ligne 6 est un appel système permettant de récupérer une référence vers le gestionnaire de capteurs.

Les lignes 7 à 10 permettent d'enregistrer un écouteur donné auprès du système. On y précise le type de capteur voulu, ainsi que le taux de rafraichissement maximum souhaité. Cette fréquence doit être l'une des constantes :

  • SENSOR_DELAY_NORMAL : le plus lent. Utilisé par Android pour la rotation d'écran.

  • SENSOR_DELAY_UI : un peu plus rapide. Adapté au rafraichissement de l'interface utilisateur (UI = User Interface).

  • SENSOR_DELAY_GAME : encore plus rapide. Adapté aux jeux.

  • SENSOR_DELAY_FASTEST : le plus rapide possible de la machine.

Depuis Android 2.3, il est possible de remplacer l'usage de cette constante par une durée exprimée en micro-secondes (et non pas en milli-secondes comme on a l'habitude).

On pourrait donc se contenter de ces 2 lignes. Mais vous aurez noté que celles-ci sont placées dans une méthode onResume  et qu'une méthode onPause  semble retirer l'enregistrement de notre écouteur du système (ligne 16). L'usage de ces deux méthodes est recommandé pour toutes les actions de ce type, qui sont consommatrices d'énergie. Le cycle de vie d'une activité sera discuté plus pleinement dans la partie suivante. Notez d'ores et déjà que la méthode onResume  est appelée par le système après onCreate, mais avant que l'activité ne soit présentée à l'utilisateur, tandis que la méthode onResume  est appelée par le système lorsque l'activité en cours n'est plus directement visible.

En avant pour un petit exemple : un niveau à eau

Je vous propose d'utiliser un capteur très fréquemment présent sur les appareils Android (l'accéléromètre), pour faire un petit niveau à eau (qui nous indiquera la pente, en pourcentage, dans les 2 dimensions du plan).

Mise en place du visuel de l'application

Comme souvent, pour faire une application, commencez par faire ce qui est facile et qui permet de dégrossir le travail. Ainsi, commençons par le visuel. Je vous propose que ce soit simplement 2 TextView placés sur 2 bords de l'écran et une petite flèche de part et d'autre pour rappeler l'axe dont on parle :

Apparence proposée pour le niveau à eau
Apparence proposée pour le niveau à eau

Vous n'aurez pas de mal à placer les 2 TextView  (que l'on pourra nommer deltax et deltay). Simplement, pour les flèches proposées, qui font partie de la bibliothèque Android, il suffit de placer unImageView  dans votre interface et de choisir parmi les visuels proposés (ceux de votre dossier draw, augmenté de ceux du système). Pour les flèche des ordonnées, cela donne :

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:srcCompat="@android:drawable/arrow_down_float"
    android:layout_marginTop="8dp"
    app:layout_constraintTop_toBottomOf="@+id/deltay"
    android:layout_marginLeft="16dp"
    app:layout_constraintLeft_toLeftOf="parent" />

<ImageView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:srcCompat="@android:drawable/arrow_up_float"
    android:layout_marginBottom="8dp"
    app:layout_constraintBottom_toTopOf="@+id/deltay"
    android:layout_marginLeft="16dp"
    app:layout_constraintLeft_toLeftOf="parent" ></ImageView>

Cependant, la bibliothèque ne propose que des flèches verticales. Pour l'axe des ordonnées, il faudra donc simplement appliquer une rotation :

<ImageView
    android:id="@+id/imageView"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_marginRight="7dp"
    android:layout_marginTop="16dp"
    android:rotation="-90"
    app:layout_constraintRight_toLeftOf="@+id/deltax"
    app:layout_constraintTop_toTopOf="parent"
    app:srcCompat="@android:drawable/arrow_up_float" />

<ImageView
    android:id="@+id/imageView2"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:srcCompat="@android:drawable/arrow_down_float"
    app:layout_constraintLeft_toRightOf="@+id/deltax"
    android:rotation="-90"
    android:layout_marginLeft="8dp"
    app:layout_constraintTop_toTopOf="parent"
    android:layout_marginTop="16dp" ></ImageView>

Une fois le visuel réalisé, il faut récupérer une référence Java vers chacun des deux TextView  créés pour les rendre utilisables par la suite. Donc, après avoir déclaré 2 attributs de type  TextView  :

private TextView tvDeltaX;
private TextView tvDeltaY;

initialisez-les dans le onCreate  de notre activité :

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

    tvDeltaX = (TextView) findViewById(R.id.deltax);
    tvDeltaY = (TextView) findViewById(R.id.deltay);
}

Mise en place de la gestion des capteurs

Je vous propose d'utiliser notre activité comme écouteur pour l'accéléromètre. Auquel cas, c'est elle qui doit répondre au contrat SensorEventListener  :

public class MainActivity (...) implements SensorEventListener {

Cette activité doit donc fournir les 2 méthodes de traitement des événements :

public void onSensorChanged(SensorEvent event) {
    tvDeltaX.setText((int)(event.values[0]*100/SensorManager.GRAVITY_EARTH)+"%");
    tvDeltaY.setText((int)(event.values[1]*100/SensorManager.GRAVITY_EARTH)+"%");
}
public void onAccuracyChanged(Sensor sensor, int accuracy) { }

Les valeurs lues pour les axes qui nous intéressent étant reçues dans les cases 0 et 1 du tableau values  de event,  les expressions proposées permettront de mettre à jour le texte des TextViewavec une conversion en pourcentage des valeurs. Notez que l'on peut utiliser directement la connaissance de la valeur de la gravité terrestre sans avoir à chercher partout dans Wikipedia ;).

Ensuite, il ne reste presque qu'à enregistrer notre application auprès du gestionnaire de l'accéléromètre pour que cela soit fini. Vu que l'on cherche à capturer la gravité, on utilisera le capteur TYPE_GRAVITY. Pour ce qui est du rafraichissement, vu que notre niveau est sensé ne pas bouger, je propose une vitesse faible, qui économisera nos yeux et les batteries du device ;) :

private SensorManager sensorManager;

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

    sensorManager =  (SensorManager) getSystemService(SENSOR_SERVICE);
    sensorManager.registerListener(
            this,
            sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY),
            SensorManager.SENSOR_DELAY_NORMAL);
}

@Override
protected void onPause() {
    super.onPause();
    sensorManager.unregisterListener(this);
}

Enfin, comme notre application ne fonctionnerait pas sans l'accéléromètre, il est préférable le préciser dans le Manifest :

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

Alors, votre bureau à vous aussi est il un peu incliné ? :'(

Pour finir sur les capteurs

Plusieurs sondes avec un seul écouteur ?

Dans certains cas, nous souhaiterions qu'un même écouteur sont enregistré auprès de plusieurs sondes (par exemple pour faire un calcul dépendant en même temps de la vitesse de rotation et de la luminosité ambiante).

protected void onResume() {
    super.onResume();
    sensorManager =  (SensorManager) getSystemService(SENSOR_SERVICE);

    // 3 sondes, avec le même écouteur, mais des fréquences différentes
    sensorManager.registerListener(
        this, 
        sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER),
        SensorManager.SENSOR_DELAY_FASTEST);

    sensorManager.registerListener(
        this, 
        sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE),
        SensorManager.SENSOR_DELAY_UI);

    sensorManager.registerListener(
        this, 
        sensorManager.getDefaultSensor(Sensor.LIGHT),
        SensorManager.SENSOR_DELAY_NORMAL);
}

Dans ces cas là, l'écouteur sera appelé pour chacune des valeurs disponibles dans chacun des deux capteurs (qui peuvent avoir une vitesse de rafraichissement différente).

Il faudra donc simplement, en tête de l'écouteur, commencer par regarder quel capteur est à l'origine de l'événement. On peut retrouver cette information via le champs sensor  du SensorEvent  reçu :

@Override
public void onSensorChanged(SensorEvent event) {
    if(event.sensor.getType()==Sensor.TYPE_ACCELEROMETER) {
        //....
    } else if(event.sensor.getType()==Sensor.TYPE_GYROSCOPE) {
        //....
    } else if(event.sensor.getType()==Sensor.TYPE_LIGHT) {
        //....
    } 
}

J'ai fait des tests, et je note que mon téléphone posé sur ma table me remontre des mesures changeantes ... Que se passe-t-il ? :euh:

On mesure des grandeurs physiques avec des appareils dont la précision n'est pas parfaite. Il est donc normal que les valeurs lues oscillent en permanence ^^.

Exemple de certificat de réussite
Exemple de certificat de réussite