Tutoriel Android : apprendre à utiliser les capteurs sous Android

Android2ee

Mathias Seguy

Ce tutoriel explique comment utiliser les capteurs Android. Vous apprendrez à les comprendre de manière générale puis à les utiliser. Chaque capteur sera expliqué.

Pour réagir à ce tutoriel, un espace de dialogue vous est proposé sur le forum : Commentez ♪

- Titre : Tutoriel Android : apprendre à utiliser les capteurs sous Android

- Auteur : Mathias Seguy

- Parution : 9 janvier 2012

- Licence : Copyright ® 2020 Mathias Seguy. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- I. Introduction

- II. Les notions communes à tous les capteurs

- II-A. Le repère utilisé

- II-B. Lister les capteurs présents et écouter leur spécificité

- II-C. Utiliser un capteur et écouter ses changements de valeurs

- II-D. Comprendre le SensorEvent

- II-D-1. Les méthodes d'écoute des changements des capteurs

- II-D-2. Unités et structures des différents capteurs

- II-E. Déclarer le capteur dans votre AndroidManifest

- II-F. Gérer le changement d'orientation de l'écran (du portrait au landscape)

- II-G. Les capteurs obsolètes

- III. Le capteur de lumière

- IV. Le capteur de proximité

- V. L'accéléromètre, l'accéléromètre linéaire et la gravité

- VI. Le capteur électromagnétique

- VII. Le capteur d'orientation

- VII-A. Écoute directe du capteur d'orientation

- VII-B. Récupération de l'orientation avec le champ magnétique et l'accélération

- VII-C. Digression graphique

- VII-D. La méthode remapCoordinateSystem

- VIII. Le gyroscope

- IX. Le capteur vecteur d'orientation

- X. Conclusion

- XI. Remerciements

I - Introduction

Pour être honnête depuis que j'ai douze ans, je rêve d'avoir un objet (une montre à l'époque) avec tout un tas de capteurs. Et voilà que vingt ans plus tard mon rêve est exaucé avec les téléphones Android.

C'est donc avec plaisir que je vais essayer d'expliquer comment les utiliser. Par nature, il y aura des notions mathématiques associées à ces explications.

Je vais tout d'abord me pencher sur deux capteurs simples, celui de la lumière et celui de la proximité.

J'expliquerai les capteurs d'accélération qui sont le capteur d'accélération, le capteur de gravité et le capteur d'accélération linéaire.

Nous aborderons le capteur magnétique, le capteur d'orientation (à se pendre), le gyroscope et le vecteur de rotation.

Les capteurs de pression atmosphérique et de température ne seront pas traités (vous n'en aurez pas besoin quand vous aurez fini de lire l'article).

Mais avant toute chose, le premier chapitre est dédié à ce qui est commun à l'ensemble des capteurs. Nous abordons ainsi les capteurs en eux-mêmes (l'objet Sensor). Nous montrons comment les instancier, les utiliser pour récupérer leur changement de valeurs. Ce qui nous amène à l'étude du SensorEvent. Et nous concluons ce chapitre par la correction des valeurs du capteur en fonction de l'orientation de l'écran.

II - Les notions communes à tous les capteurs

Ce chapitre rassemble l'ensemble des notions communes aux capteurs.

II-A - Le repère utilisé

La plupart des capteurs à trois dimensions sont associés au repère orthonormé suivant :

provenant de Google http://developer.android.com/images/axis_device.png

Ce repère est celui utilisé par les capteurs accéléromètre, gyroscope, électromagnétisme. L'orientation n'utilise pas ce repère.

II-B - Lister les capteurs présents et écouter leur spécificité

Pour lister ou récupérer un capteur particulier, il suffit d'instancier le SensorManager et ses méthodes getSensorList ou getDefaultSensor.

1. // The sensor manager

2. SensorManager sensorManager;

4. @Override

5. protected void onCreate(Bundle savedInstanceState) {

6. super.onCreate(savedInstanceState);

7. // Instanicer le SensorManager

8. sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

9. // Faire la liste des capteurs de l'appareil

10. listSensor();

11. }

13. /**

14. * Lister les capteurs existants

15. *

16. * Trouve la liste de tous les capteurs existants, trouve un capteur spécifique ou l'ensemble des capteurs d'un type fixé.

17. */

18. private void listSensor() {

19. // Trouver tous les capteurs de l'appareil :

20. List<Sensor> sensors = sensorManager.getSensorList(Sensor.TYPE_ALL);

21. // La chaîne descriptive de chaque capteur

22. StringBuffer sensorDesc = new StringBuffer();

23. // pour chaque capteur trouvé, construire sa chaîne descriptive

24. for (Sensor sensor : sensors) {

25. sensorDesc.append("New sensor detected : \r\n");

26. sensorDesc.append("\tName: " + sensor.getName() + "\r\n");

27. sensorDesc.append("\tType: " + getType(sensor.getType()) + "\r\n");

28. sensorDesc.append("Version: " + sensor.getVersion() + "\r\n");

29. sensorDesc.append("Resolution (in the sensor unit): " + sensor.getResolution() + "\r\n");

30. sensorDesc.append("Power in mA used by this sensor while in use" + sensor.getPower() + "\r\n");

31. sensorDesc.append("Vendor: " + sensor.getVendor() + "\r\n");

32. sensorDesc.append("Maximum range of the sensor in the sensor's unit." + sensor.getMaximumRange() + "\r\n");

33. sensorDesc.append("Minimum delay allowed between two events in microsecond" + " or zero if this sensor only returns a value when the data it's measuring changes" + sensor.getMinDelay() + "\r\n");

34. }

36. // Faire quelque chose de cette liste

37. Toast.makeText(this, sensorDesc.toString(), Toast.LENGTH_LONG).show();

39. // Pour trouver un capteur spécifique :

40. Sensor gyroscopeDefault = sensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE);

41. // Pour trouver tous les capteurs d'un type fixé :

42. List<Sensor> gyroscopes = sensorManager.getSensorList(Sensor.TYPE_GYROSCOPE);

43. }

45. private String getType(int type) {

46. String strType;

47. switch (type) {

48. case Sensor.TYPE_ACCELEROMETER: strType = "TYPE_ACCELEROMETER";break;

49. case Sensor.TYPE_GRAVITY:strType = "TYPE_GRAVITY";break;

50. case Sensor.TYPE_GYROSCOPE:    strType = "TYPE_GYROSCOPE";    break;

51. case Sensor.TYPE_LIGHT:strType = "TYPE_LIGHT";break;

52. case Sensor.TYPE_LINEAR_ACCELERATION:strType = "TYPE_LINEAR_ACCELERATION";

53. break;

54. case Sensor.TYPE_MAGNETIC_FIELD:strType = "TYPE_MAGNETIC_FIELD";break;

55. case Sensor.TYPE_ORIENTATION:strType = "TYPE_ORIENTATION";break;

56. case Sensor.TYPE_PRESSURE:strType = "TYPE_PRESSURE";break;

57. case Sensor.TYPE_PROXIMITY:    strType = "TYPE_PROXIMITY";    break;

58. case Sensor.TYPE_ROTATION_VECTOR:    strType = "TYPE_ROTATION_VECTOR";break;

59. case Sensor.TYPE_TEMPERATURE:strType = "TYPE_TEMPERATURE";break;

60. default: strType = "TYPE_UNKNOW";break;

61. }

62. return strType;

63. }

II-C - Utiliser un capteur et écouter ses changements de valeurs

Quand on souhaite travailler avec les capteurs, la première chose à faire est de savoir écouter leur changement d'état (de valeurs). Pour cela, c'est assez simple, il suffit de les instancier puis de s'enregistrer en tant qu'écouteur dans la méthode onResume et de se désenregistrer dans la méthode onPause. Pour les écouter, il suffit d'implémenter l'interface SensorEventListener (souvent au niveau de votre activité).

Au lieu d'un long discours, quelques lignes de code éclaireront cette explication. Prenons l'exemple de l'accéléromètre :

1. public class SensorAccelerationTutoActivity extends Activity implements SensorEventListener {

3. // Déclaration de l'attribut en tant qu'attribut de l'activité

4. // Le sensor manager (gestionnaire de capteurs)

5. SensorManager sensorManager;

7. // L'accéléromètre

8. Sensor accelerometer;

10. /***************************************************************************/

11. /** Manage life cycle ******************************************************/

12. /***************************************************************************/

14. // Appelé à la création de l'activité. */

15. @Override

16. public void onCreate(Bundle savedInstanceState) {

17. super.onCreate(savedInstanceState);

18. // Faire quelque chose

19. // Gérer les capteurs :

20. // Instancier le gestionnaire des capteurs, le SensorManager

21. sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

22. // Instancier l'accéléromètre

23. accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

24. // Faire d'autres trucs

25. }

27. @Override

28. protected void onPause() {

29. // unregister the sensor (désenregistrer le capteur)

30. sensorManager.unregisterListener(this, accelerometer);

31. super.onPause();

32. }

34. @Override

35. protected void onResume() {

36. /* Ce qu'en dit Google dans le cas de l'accéléromètre :

37. * «  Ce n'est pas nécessaire d'avoir les évènements des capteurs à un rythme trop rapide.

38. * En utilisant un rythme moins rapide (SENSOR_DELAY_UI), nous obtenons un filtre

39. * automatique de bas niveau qui "extrait" la gravité  de l'accélération.

40. * Un autre bénéfice étant que l'on utilise moins d'énergie et de CPU. »

41. */

42. sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);

43. super.onResume();

44. }

46. /********************************************************************/

47. /** SensorEventListener*************************************************/

48. /********************************************************************/

50. @Override

51. public void onAccuracyChanged(Sensor sensor, intaccuracy) {

52. // Rien à faire la plupart du temps

53. }

55. @Override

56. public void onSensorChanged(SensorEvent event) {

57. // Récupérer les valeurs du capteur

58. floatx, y, z;

59. if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {

60. x = event.values[0];

61. y = event.values[1];

62. z = event.values[2];

63. }

64. }

Cet exemple est celui de l'accéléromètre, mais il s'applique à tous les capteurs.

II-D - Comprendre le SensorEvent

II-D-1 - Les méthodes d'écoute des changements des capteurs

Dans l'exemple suivant, je montre comment utiliser les trois champs du SensorEvent qui sont utiles : accuracy, timestamp et values.

Dans les exemples qui suivent, le seul objectif est de présenter les différents champs.

1. @Override

2. public void onSensorChanged(SensorEvent event) {

3. // Tout d'abord vérifier que le capteur est bien celui que l'on écoute (il y a un champ sensor nommé

4. // litenedSensor dans la classe qui est le capteur que l'on écoute).

5. if (event.sensor.equals(listenedSensor)) {

6. // On trouve sa précision

7. int accuracy = event.accuracy;

8. // On l'analyse

9. switch (accuracy) {

10. case SensorManager.SENSOR_STATUS_ACCURACY_LOW:

11. case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM:

12. case SensorManager.SENSOR_STATUS_ACCURACY_HIGH:

13. case SensorManager.SENSOR_STATUS_UNRELIABLE:

14. default:    break;

15. }

17. // On trouve le moment où l'évènement a été émis

18. longtimestamp = event.timestamp;

19. // On récupère ses valeurs (cela sera expliqué plus tard)

20. float[] val = event.values;

21. // et on fait quelque chose de ces informations

22. }

23. }

25. @Override

26. public void onAccuracyChanged(Sensor sensor, int accuracy) {

27. // Tout d'abord vérifier que le capteur est bien celui que l'on écoute (il y a un champ sensor nommé

28. // litenedSensor dans la classe qui est le capteur que l'on écoute).

29. if(sensor.equals(listenedSensor)){

30. // On analyse sa précision

31. switch (accuracy) {

32. case SensorManager.SENSOR_STATUS_ACCURACY_LOW:

33. case SensorManager.SENSOR_STATUS_ACCURACY_MEDIUM:

34. case SensorManager.SENSOR_STATUS_ACCURACY_HIGH:

35. case SensorManager.SENSOR_STATUS_UNRELIABLE:

36. default:

37. break;

38. }

40. // et on fait quelque chose avec ça

41. }

42. }

44. // Ci-dessous les différentes valeurs du rythme de récupération des données. En d'autres termes, à la

45. // vitesse d'émission de l'évènement SensorEvent

46. // La vitesse par défaut

47. int rate=SensorManager.SENSOR_DELAY_NORMAL;

49. // La vitesse à utiliser pour les jeux

50. rate=SensorManager.SENSOR_DELAY_GAME;

52. // La vitesse la plus rapide qui puisse être

53. rate=SensorManager.SENSOR_DELAY_FASTEST;

55. // La vitesse si l'on souhaite uniquement mettre à jour l'UI

56. rate=SensorManager.SENSOR_DELAY_UI;

58. // L'objectif étant de s'enregistrer avec cette vitesse :

59. sensorManager.registerListener(sensorOrientationListener, listenedSensor, rate);

60. }

II-D-2 - Unités et structures des différents capteurs

Chaque capteur renvoie un vecteur de données sous forme de flottant (ce vecteur peut être d'une à trois dimensions). Le tableau ci-dessous synthétise la sémantique associée à ses vecteurs :

Nom

Dimension du vecteur

Unité

Sémantique

Values[]

Accelerometer

3

m/s2

Mesure de l'accélération (gravité incluse)

[0] axe x
[1] axe y
[2] axe z

Gyroscope

3

Radian/seconde

Mesure la rotation en termes de vitesse autour de chaque axe

[0] vitesse angulaire autour de x
[1] vitesse angulaire autour de y
[2] vitesse angulaire autour de z

Light

1

Lux

Mesure de la luminosité

[0]valeur

Magnetic_Field

3

µTesla

Mesure du champ magnétique

[0] axe x
[1] axe y
[2] axe z

Orientation

3

degrés

Mesure l'angle entre le nord magnétique

[0] Azimut entre l'axe y et le nord
[1] Rotation autour de l'axe x (-180,180)
[2] Rotation autour de l'axe y (-90,90)

Pressure

1

KPascal

Mesure la pression

[0]valeur

Proximity

1

mètre

Mesure la distance entre l'appareil et un objet cible

[0]valeur

Temperature

1

Celsius

Mesure la température

[0]valeur

Pour utiliser certains de ces capteurs, un minimum de connaissance en physique est nécessaire.

II-E - Déclarer le capteur dans votre AndroidManifest

Ce détail est d'importance, en effet, l'Android Market analyse votre manifeste et proposera votre application uniquement aux appareils possédant le capteur que vous avez déclaré. Cette déclaration s'effectue comme suit :

1. <manifest xmlns:android="http://schemas.android.com/apk/res/android"

2. package="com.android2ee.android.tuto.sensor.light"android:versionCode="1"

3. android:versionName="1.0">

4. <uses-sdkandroid:minSdkVersion="10" />

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

6. <uses-featureandroid:name="android.hardware.sensor.barometer" android:required="true" />

7. <uses-featureandroid:name="android.hardware.sensor.compass" android:required="true" />

8. <uses-featureandroid:name="android.hardware.sensor.gyroscope" android:required="true" />

9. <uses-featureandroid:name="android.hardware.sensor.light" android:required="true" />

10. <uses-featureandroid:name="android.hardware.sensor.proximity" android:required="true" />

11. </manifest>

L'exemple précédent déclare tous les capteurs connus, mais spécifie uniquement celui dont vous avez besoin pour votre application.

II-F - Gérer le changement d'orientation de l'écran (du portrait au landscape)

Une problématique qui s'applique à la plupart des capteurs est la gestion du mode de l'écran (landscape ou portrait). L'idée est de savoir, quand on récupère les valeurs du capteur, l'état de l'orientation de l'écran. En fonction de cet état, on corrige les valeurs :

1. // En attribut de la classe (de l'activité), le seul qui connaisse l'orientation de l'appareil

2. private Display mDisplay;

4. @Override

5. public void onCreate(Bundle savedInstanceState) {

6. super.onCreate(savedInstanceState);

7. // Faire quelque chose

8. // Gérer les capteurs :

9. // Instancier le gestionnaire des capteurs, le SensorManager

10. sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

11. // Instancier l'accéléromètre

12. accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

13. // Faire d'autres trucs

14. }

16. @Override

17. public void onSensorChanged(SensorEvent event) {

18. // Récupérer les valeurs du capteur

19. float x, y, z;

20. if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {

21. // Log.d(LOG_TAG, "TYPE_ACCELEROMETER");

22. // En fonction de l'orientation de l'appareil, on corrige les valeurs x et y du capteur

23. switch (mDisplay.getRotation()) {

24. case Surface.ROTATION_0:

25. x = event.values[0];

26. y = event.values[1];

27. break;

28. case Surface.ROTATION_90:

29. x = -event.values[1];

30. y = event.values[0];

31. break;

32. case Surface.ROTATION_180:

33. x = -event.values[0];

34. y = -event.values[1];

35. break;

36. case Surface.ROTATION_270:

37. x = event.values[1];

38. y = -event.values[0];

39. break;

40. }

42. // la valeur de z

43. z = event.values[2];

44. // faire quelque chose, par exemple un Log :

45. Log.d(LOG_TAG, "Sensor's values ("+x+","+y+","+z+") and maxRange : "+maxRange);

46. }

47. }

Vous remarquerez qu'il n'y a pas deux états (landscape ou portrait), mais quatre, landscape, landscape inversé, portrait, portrait inversé. L'exemple s'appuie sur l'utilisation de l'accéléromètre qui est l'exemple typique.

II-G - Les capteurs obsolètes

Le capteur Sensor.Temperature() est obsolète, il est préconisé d'utiliser Sensor. TYPE_AMBIENT_TEMPERATURE.

De même Sensor.Orientation, il est préconisé d'utiliser SensorManager.getOrientation(). Cela est expliqué au chapitre concernant ce capteur.

Cette obsolescence dépend de la version du SDK de votre application, à vous de le vérifier.

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

III - Le capteur de lumière

Ce capteur est extrêmement facile d'utilisation, mais sa sensibilité dépend du fabricant. Typiquement le mien ne renvoie que des puissances de 10 (10, 100, 1000, et ainsi de suite). Ce capteur permet de savoir quelle est l'intensité lumineuse détectée par votre téléphone (l'unité est le Lux).

Ainsi pour écouter les changements de valeur de ce capteur, il vous faut :

- le déclarer dans votre fichier AndroidManifest le capteur de lumière ;

- faire étendre votre activité (ou la classe qui écoute le capteur) de SensorListener ;

- déclarer et instancier un objet Sensor de type light ;

- s'enregistrer/se désenregistrer en tant qu'écouteur de ce capteur ;

- faire quelque chose lors d'un changement de valeur.

Ce qui donne :

public class SensorLightTutoActivity extends Activity implements SensorEventListener {
/*********************************************************************/
/** Attribut du capteur***********************************************/
/*********************************************************************/

// Valeur courante de la lumière
float l;

// Le sensor manager
SensorManager sensorManager;

// Le capteur de lumière
Sensor light;

/***************************************************************/
/** Gestion du cycle de vie ************************************/
/***************************************************************/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// construire l'IHM
setContentView(R.layout.main);

// Instancier le SensorManager
sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
// Instancier le capteur de lumière
light = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT);
}

@Override
protected void onPause() {
// désenregistrer notre écoute du capteur
sensorManager.unregisterListener(this, light);
super.onPause();
}

@Override
protected void onResume() {
// enregistrer notre écoute du capteur
sensorManager.registerListener(this, light, SensorManager.SENSOR_DELAY_GAME);
super.onResume();
}

/*****************************************************************/
/** SensorEventListener ******************************************/
/*****************************************************************/

@Override
public void onAccuracyChanged(Sensor sensor, intaccuracy) {
// Faîtes quelque chose ou pas&#8230;
}

@Override
public void onSensorChanged(SensorEvent event) {
// Mettre à jour uniquement dans le cas de notre capteur
if (event.sensor.getType() == Sensor.TYPE_LIGHT) {
// La valeur de la lumière
l = event.values[0];
// faire autre chose&#8230;
}
}
}

IV - Le capteur de proximité

Ce capteur est extrêmement facile d'utilisation, mais sa sensibilité dépend du fabricant. Typiquement le mien ne renvoie que soit 0, soit 5 mètres. Pour comprendre pourquoi il faudrait le nommer « capteur qui détecte la présence du corps humain au niveau de l'écouteur de l'appareil ». S'il détecte une présence il renvoie 0 sinon il renvoie 5. Comment ça marche ? Il paraît que cela marche via la réflexion de la lumière ou du son…

Ainsi pour écouter les changements de valeur de ce capteur, il vous faut (comme d'habitude) :

- déclarer dans votre fichier AndroidManifest le capteur de proximité ;

- faire étendre votre activité (ou la classe qui écoute le capteur) de SensorListener ;

- déclarer et instancier un objet Sensor de type proximity ;

- s'enregistrer/se désenregistrer en tant qu'écouteur de ce capteur ;

- faire quelque chose lors d'un changement de valeur.

Bon, le principe est identique à celui de la lumière, ce qui donne :

1. public class SensorProximityTutoActivity extends Activity implements SensorEventListener {

2. /*********************************************************************/

3. /** Attribut du capteur***********************************************/

4. /*********************************************************************/

6. // Valeur courante de la proximité

7. float p;

9. // Le sensor manager

10. SensorManager sensorManager;

12. // Le capteur de proximité

13. Sensor proximity;

15. /***************************************************************/

16. /** Gestion du cycle de vie ************************************/

17. /***************************************************************/

19. @Override

20. public void onCreate(Bundle savedInstanceState) {

21. super.onCreate(savedInstanceState);

22. // construire l'IHM

23. setContentView(R.layout.main);

25. // Instancier le SensorManager

26. sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

27. // Instancier le capteur de lumière

28. proximity = sensorManager.getDefaultSensor(Sensor.TYPE_PROXIMITY);

29. }

31. @Override

32. protected void onPause() {

33. // désenregistrer notre écoute du capteur

34. sensorManager.unregisterListener(this, proximity);

35. // and don't forget to pause the thread that redraw the xyAccelerationView

36. super.onPause();

37. }

39. @Override

40. protected void onResume() {

41. // Enregistrer notre écoute du capteur

42. sensorManager.registerListener(this, proximity, SensorManager.SENSOR_DELAY_GAME);

43. super.onResume();

44. }

46. /*****************************************************************/

47. /** SensorEventListener *********************************************/

48. /*****************************************************************/

50. @Override

51. public void onAccuracyChanged(Sensor sensor, intaccuracy) {

52. // Faites  quelque chose ou pas&#8230;

53. }

55. @Override

56. public void onSensorChanged(SensorEvent event) {

57. // Mettre à jour uniquement dans le cas de notre capteur

58. if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {

59. // La valeur de la lumière

60. p = event.values[0];

61. // faire autre chose&#8230;

62. }

63. }

64. }

V - L'accéléromètre, l'accéléromètre linéaire et la gravité

Ce capteur est celui qui me plait le plus, il est capable de donner le champ de forces qui s'appliquent sur l'appareil (je peux détecter la présence des Jedis… OK, je sors). À partir de ce capteur Android en dérive deux de plus, le Linear Acceleration et la Gravity.

L'accéléromètre fournit le vecteur de force (ou d'accélération, c'est la même chose) tridimensionnel (x,y,z). À partir de ce vecteur, le système déduit la composante gravitationnelle (toujours un vecteur tridimensionnel). Celle-ci est celle renvoyée par le capteur Gravity. Le capteur Linear Acceleration déduit du champ de force cette composante fournissant un vecteur (tridimensionnel) épuré de la gravité. Cela explique pourquoi les capteurs Gravity et Linear Acceleration n'ont pas de numéro de série et sont fournis par Google.

Il est important de comprendre le repère utilisé par ce capteur :

provenant de Google http://developer.android.com/images/axis_device.png

Ainsi le vecteur d'accélération (x,y,z) est toujours donné en fonction du repère de l'écran. Si vous bougez votre écran, ces valeurs vont changer. L'axe des X est l'axe horizontal de l'écran, l'axe des Y est le vertical et l'axe des z est orthogonal à l'écran.

Au repos, la gravité est dans le repère terrestre (0,0,), dans le repère de l'appareil vous aurez donc des valeurs qui dépendent de l'inclinaison de l'appareil dans l'espace.

De ce capteur vous pouvez effectuer une multitude de choses (niveau à bulle, jeux contrôlés par l'accélération…)

Dans le tutoriel, j'ai mis en place deux balles qui sont manipulées par le champ de forces ; l'une considère ce champ comme son vecteur d'accélération, l'autre comme son vecteur vitesse. Si vous souhaitez faire un jeu dont les trajectoires des objets sont contrôlées par le champ de force, le mieux est d'inventer vos propres formules.

Le code permet d'écouter l'un des trois capteurs. Les trois capteurs sont enregistrés et un entier sensorType permet d'écouter spécifiquement l'un de ces capteurs.

Dans le fichier de l'activité (j'ai épuré le code, un copier-coller ne marchera pas, utiliser le tutoriel plutôt) :

1. public class SensorAccelerationTutoActivity extends Activity implements SensorEventListener {

2. // Valeur courante de l'accéléromètre

3. float x, y, z;

5. /*************************************************************************************/

6. /** Sensors and co *******************************************************************/

7. /*************************************************************************************/

9. // Celui qui connait l'orientation de l'appareil

10. private Display mDisplay;

12. // Le sensor manager

13. SensorManager sensorManager;

15. // L'accéléromètre

16. Sensor accelerometer;

18. // La gravité

19. Sensor gravity;

21. // L'accéléromètre linéaire

22. Sensor linearAcc;

24. /**************************************************************/

25. /** Sensors Type Constant *************************************/

26. /**************************************************************/

28. // Le capteur sélectionné

29. private int sensorType;

31. // L'accéléromètre

32. private static final int ACCELE = 0;

34. // La Gravité

35. private static final int Gravity = 1;

37. // L'accéléromètre linéaire

38. private static final int LINEAR_ACCELE = 2;

40. /*****************************************/

41. /** Gestion du cycle de vie **************/

42. /*****************************************/

44. @Override

45. public void onCreate(Bundle savedInstanceState) {

46. super.onCreate(savedInstanceState);

47. // Construire l'IHM

48. setContentView(R.layout.main);

49. // Gérer les capteurs

50. // Instancier le SensorManager

51. sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

52. // Instancier l'accéléromètre

53. accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

54. // Instancier la gravité

55. gravity = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY);

56. // Instancier l'accélération linéaire

57. linearAcc = sensorManager.getDefaultSensor(Sensor.TYPE_LINEAR_ACCELERATION);

58. // Et enfin instancier le display qui connaît l'orientation de l'appareil

59. mDisplay = ((WindowManager) getSystemService(WINDOW_SERVICE)).getDefaultDisplay();

60. }

62. @Override

63. protected void onPause() {

64. // désenregistrer tous le monde

65. sensorManager.unregisterListener(this, accelerometer);

66. sensorManager.unregisterListener(this, gravity);

67. sensorManager.unregisterListener(this, linearAcc);

68. super.onPause();

69. }

71. @Override

72. protected void onResume() {

73. /* Ce qu'en dit Google :

74. * «  Ce n'est pas nécessaire d'avoir les évènements des capteurs à un rythme trop rapide.

75. * En utilisant un rythme moins rapide (SENSOR_DELAY_UI), nous obtenons un filtre

76. * automatique de bas niveau qui "extrait" la gravité  de l'accélération.

77. * Un autre bénéfice étant que l'on utilise moins d'énergie et de CPU. »

78. */

79. sensorManager.registerListener(this, accelerometer, SensorManager.SENSOR_DELAY_UI);

80. sensorManager.registerListener(this, gravity, SensorManager.SENSOR_DELAY_UI);

81. sensorManager.registerListener(this, linearAcc, SensorManager.SENSOR_DELAY_UI);

82. super.onResume();

83. }

85. /*********************************/

86. /** SensorEventListener **********/

87. /*********************************/

89. @Override

90. public void onAccuracyChanged(Sensor sensor, intaccuracy) {

91. // Nothing to do

92. }

94. @Override

95. public void onSensorChanged(SensorEvent event) {

96. // update only when your are in the right case:

97. if (((event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) && (sensorType == ACCELE))

98. || ((event.sensor.getType() == Sensor.TYPE_GRAVITY) && (sensorType == Gravity))

99. || ((event.sensor.getType() == Sensor.TYPE_LINEAR_ACCELERATION) && (sensorType == LINEAR_ACCELE))) {

100. // Corriger les valeurs x et y en fonction de l'orientation de l'appareil

101. switch (mDisplay.getRotation()) {

102. case Surface.ROTATION_0:

103. x = event.values[0];

104. y = event.values[1];

105. break;

106. case Surface.ROTATION_90:

107. x = -event.values[1];

108. y = event.values[0];

109. break;

110. case Surface.ROTATION_180:

111. x = -event.values[0];

112. y = -event.values[1];

113. break;

114. case Surface.ROTATION_270:

115. x = event.values[1];

116. y = -event.values[0];

117. break;

118. }

119. // la valeur z

120. z = event.values[2];

121. // faire quelque chose

122. }

123. }

124. }

Le tutoriel associé vous montre comment :

- mettre en place un spinner permettant de choisir quel type de capteur l'on souhaite écouter ;

- mettre en place trois progressBars présentant les valeurs x,y,z du vecteur de force ;

- afficher un point qui représente ce vecteur (et surtout comment mettre une thread qui redessine régulièrement l'écran) ;

- et deux points qui bougent en fonction des valeurs du capteur.

VI - Le capteur électromagnétique

Le capteur électromagnétique permet de connaître les valeurs du vecteur électromagnétique qui s'applique à votre téléphone. Le référentiel est le même que celui utilisé par le capteur d'accélération.

Les valeurs sont en micro-tesla.

Au-delà du vecteur en lui-même, sa norme permet de savoir ce que l'on appelle la valeur électromagnétique.

Si vous placez votre appareil à proximité d'un aimant (par exemple des baffles) vous verrez les valeurs augmenter.

Mais place au code, ci-dessous la méthode onSensorChanged (les méthodes onCreate, onPause, onResume et onAccuracyChanged étant semblables je ne les répéterai plus) :

1. public void onSensorChanged(SensorEvent event) {

2. // Lire les données quand elles correspondent à notre capteur:

3. if (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {

4. // Valeur du vecteur du champ magnétique (x,y,z)

5. xMagnetic = event.values[0];

6. yMagnetic = event.values[1];

7. zMagnetic = event.values[2];

8. // Valeur de la norme de ce vecteur

9. magneticStrenght=Math.sqrt((double)

10. (xMagnetic*xMagnetic + yMagnetic*yMagnetic + zMagnetic*zMagnetic));

11. // faire quelque chose, demander à mettre à jour l'IHM, par exemple :

12. redraw();

13. }

14. }

VII - Le capteur d'orientation

Comme son nom l'indique, ce capteur donne l'orientation (le nord pour être grossier). Ce capteur est la boussole de votre appareil. Il y a trois notions à comprendre avec ce capteur :

- l'azimut : donne l'angle avec le nord magnétique ;

- le pitch : donne l'angle autour de l'axe des x. En français cela se dit le tangage, mais comme cela embrouille plus qu'autre chose, je garde la nomenclature anglaise pour être en cohérence avec la SDK ;

- le roll : donne l'angle autour de l'axe des y. En français cela se dit le roulis, mais comme cela embrouille plus qu'autre chose, je garde la nomenclature anglaise pour être en cohérence avec la SDK.

L'azimut varie entre 0 et 360°, il représente l'angle avec le nord dans le sens des aiguilles d'une montre.

Le pitch varie entre -180 et 180, il représente l'inclinaison haut-bas de l'appareil selon l'axe Y (parallèle au sol, perpendiculaire au sol). On obtient les valeurs suivantes :

- la valeur 0, l'appareil est parallèle au sol, face vers le ciel ;

- la valeur +/- 180 est l'appareil parallèle au sol, face vers le sol ;

- la valeur 90 est l'appareil perpendiculaire au sol face, tête vers le bas ;

- la valeur -90 est l'appareil perpendiculaire au sol face, tête vers le haut.

Enfin le roll varie entre -90 et 90, il représente l'inclinaison droite-gauche de l'appareil selon l'axe des X (si l'appareil penche à gauche ou à droite). On obtient les valeurs suivantes (quand sa face est vers le haut) :

- la valeur 0, l'appareil ne penche pas ;

- la valeur 90 est l'appareil penche à gauche ;

- la valeur -90 est l'appareil penche à droite.

Il y a deux façons distinctes pour obtenir cette orientation, soit en écoutant directement le capteur d'orientation (ce qui n'a pas l'air d'être la bonne pratique), soit en utilisant le champ magnétique et le champ de force.

VII-A - Écoute directe du capteur d'orientation

Il faut utiliser le capteur d'orientation :

1. // Le SensorManager

2. sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);

4. // Le capteur d'orientation

5. orientation = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);

7. @Override

8. public void onSensorChanged(SensorEvent event) {

9. // update only when your are in the right case:

10. if (event.sensor.getType() == Sensor.TYPE_ORIENTATION) {

11. //l' azimuth

12. x = event.values[0];

13. //le pitch

14. y = event.values[1];

15. //le roll

16. z = event.values[2];

17. }

18. }

Cette méthode n'est pas conseillée et d'après Google n'est là que pour des raisons « legacy », c'est-à-dire provenant de l'histoire de la plateforme.

VII-B - Récupération de l'orientation avec le champ magnétique et l'accélération

Cette méthode est celle conseillée par Google. Pour la mettre en place au lieu d'écouter le capteur d'orientation, il faut écouter les capteurs d'accélération et d'électromagnétisme.

1. // Attribut de la classe pour calculer  l'orientation

2. float[] acceleromterVector=newfloat[3];

3. float[] magneticVector=newfloat[3];

4. float[] resultMatrix=newfloat[9];

5. float[] values=newfloat[3];

7. // Instantiate the magnetic sensor and its max range

8. magnetic = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);

10. // Instantiate the accelerometer

11. accelerometer = sensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

13. @Override

14. public void onSensorChanged(SensorEvent event) {

15. // Mettre à jour la valeur de l'accéléromètre et du champ magnétique

16. if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {

17. acceleromterVector=event.values;

18. } elseif (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {

19. magneticVector=event.values;

20. }

22. // Demander au sensorManager la matric de Rotation (resultMatric)

23. SensorManager.getRotationMatrix(resultMatrix, null, acceleromterVector, magneticVector);

25. // Demander au SensorManager le vecteur d'orientation associé (values)

26. SensorManager.getOrientation(resultMatrix, values);

27. // l'azimuth

28. x =(float) Math.toDegrees(values[0]);

29. // le pitch

30. y = (float) Math.toDegrees(values[1]);

31. // le roll

32. z = (float) Math.toDegrees(values[2]);

33. }

En utilisant cette méthode, il faut être attentif, en effet le pitch et le roll ne sont plus les mêmes.

Le pitch varie entre -90 et 90, il représente l'inclinaison haut-bas de l'appareil selon l'axe Y (parallèle au sol, perpendiculaire au sol). On obtient les valeurs suivantes :

- la valeur 0, l'appareil est parallèle au sol, face vers le ciel ou l'appareil est parallèle au sol ; face vers le sol ;

- la valeur 90 l'appareil est perpendiculaire au sol face, tête vers le bas ;

- la valeur -90 l'appareil est perpendiculaire au sol face, tête vers le haut.

Enfin le roll varie entre -180 et 180, il représente l'inclinaison droite-gauche de l'appareil selon l'axe des X (si l'appareil penche à gauche ou à droite). On obtient les valeurs suivantes (quand sa face est vers le haut) :

- la valeur 0, l'appareil ne penche pas, il est face au ciel ;

- la valeur 90 est l'appareil penche à gauche ;

- la valeur -90 est l'appareil penche à droite ;

- La valeur 180, l'appareil ne penche pas, il est face contre sol.

VII-C - Digression graphique

Ce capteur amène naturellement la question de dessiner ces valeurs. Pour cela quelques notions de dessins 2D sont nécessaires. Pour comprendre la philosophie de tels dessins, il faut comprendre les transformations de l'objet Canvas. En effet, on commence par tourner le Canvas (on lui applique la rotation qui correspond à la valeur que l'on affiche), ensuite on effectue le dessin (cercle, demi-cercle, flèche) dans ce nouveau référentiel. Ce qui nous permet de faire un dessin simple (toujours dessiner verticalement) qui sera tourné automatiquement par la rotation que nous appliquons au Canvas. L'idée étant de faire toujours le même dessin puis de tourner la feuille sur laquelle on a fait ce dessin, pour qu'il corresponde à notre souhait. Et ça c'est malin comme idée.

Le dessin à effectuer est le suivant :

Ainsi pour dessiner le roll, le code suivant est le bon :

1. // Sauver la configuration du canvas (orientation, translation)

2. canvas.save();

4. // trouver la valeur du roll à afficher

5. floatroll = activity.z;

7. // faire tourner le canvas, de manière à ce que la description de notre dessin s'effectue dans un repère

8. // orthonormé normal (y vers le haut), mais à ce que son affichage soit le y vers l'angle du roll

9. canvas.rotate(roll, 0, 0);

11. // Définir le rectangle contenant le cercle que l'on va dessiner

12. RectF pitchOval = new RectF(0,0, lenght, lenght);

14. // Dessiner le cercle de fond

15. paint.setColor(backgroundCircleColor);

16. canvas.drawArc(pitchOval, 0, 360, false, paint);

18. // dessiner le demi-cercle qui représente le roll (et là grâce à la rotation du canvas, on le dessine comme étant le demi-cercle inférieur)

19. paint.setColor(circleColor);

20. canvas.drawArc(pitchOval, 0, 180, false, paint);

22. // Pour finir, revenir à la configuration du canvas initiale &#8216;il ne prend plus en compte notre rotation dans sa future //utilisation)

23. canvas.restore();

25. // Sauver la configuration initiale du canvas

26. canvas.save();

28. // Trouver le pitch à afficher

29. floatpitch = activity.y;

31. // Mettre le centre au milieu de l'écran, dans son quart bas

32. canvas.translate(cx, cy + 3 * lenght / 2);

34. // Définir le rectangle contenant le cercle à dessiner

35. RectF pitchOval = new RectF(-lenght/2, -lenght/2, lenght/2, lenght/2);

37. // Dessiner le cercle du fond

38. paint.setColor(backgroundCircleColor);

39. canvas.drawCircle(0, 0, lenght / 2, paint);

41. // Dessiner le pitch

42. paint.setColor(circleColor);

43. if(pitch<=0) {

44. canvas.drawArc(pitchOval, -90-pitch, 360+2*pitch, false, paint);

45. }else {

46. canvas.drawArc(pitchOval, 90+pitch, 360-2*pitch, false, paint);

47. }

49. // restaurer l'état initial du canvas

50. canvas.restore();

51. Pour l'azimut, ou comment dessiner une boussole :

52. canvas.save();

54. // Définir la flèche nord

55. Path northPath = new Path();

56. Path southPath = new Path();

57. northPath.moveTo(0, -60);

58. northPath.lineTo(-10, 0);

59. northPath.lineTo(10,0);

60. northPath.close();

62. // Définir la flèche sud

63. southPath.moveTo(-10,0);

64. southPath.lineTo(0,60);

65. southPath.lineTo(10,0);

66. southPath.close();

67. floatfontHeight = paint.getFontMetrics().ascent  +  paint.getFontMetrics().descent;

69. // Retrouver l'azimut à afficher

70. floatazimut = -activity.x;

72. // Centrer le compas au milieu de l'écran

73. canvas.translate(cx, cy);

75. // Effectuer une rotation de canvas, de sorte que la flèche indique le nord

76. // quand on la dessine verticalement

77. canvas.rotate(azimut);

79. // Dessiner le cercle de la boussole

80. paint.setColor(backgroundCircleColor);

81. canvas.drawCircle(0, 0, lenght / 2, paint);

83. // Dessiner la graduation du compas

84. paint.setColor(Color.WHITE);

85. floathText = - lenght/2 - fontHeight+3;

87. // Tous les 15° faire une graduation

88. intstep = 15;

89. for (intdegree = 0; degree < 360; degree = degree + step) {

90. // Si ce n'est pas un point cardinal, dessiner une graduation

91. if ((degree % 90) != 0) {

92. canvas.drawText("|", 0, hText, paint);

93. }

94. canvas.rotate(-step);

95. }

97. // Dessiner les points cardinaux

98. canvas.drawText("N", 0, hText, paint);

99. canvas.rotate(-90);

100. canvas.drawText("W", 0, hText, paint);

101. canvas.rotate(-90);

102. canvas.drawText("S", 0, hText, paint);

103. canvas.rotate(-90);

104. canvas.drawText("E", 0, hText, paint);

105. canvas.rotate(-90);

107. // Dessiner les flèches

108. paint.setStyle(Paint.Style.FILL);

109. paint.setColor(Color.RED);

110. canvas.drawPath(northPath, paint);

111. paint.setColor(circleColor);

112. canvas.drawPath(southPath, paint);

114. // Restaurer l'état initial du canvas

115. canvas.restore();

VII-D - La méthode remapCoordinateSystem

Cette méthode permet de réorienter l'appareil. En d'autres termes, au lieu d'être dans le système de coordonnées usuel, il est possible de définir où se trouve l'axe des X, des Y et des Z.

vers celui-ci :

Ce changement correspond à dire à l'appareil que la position de repos (usuellement, à plat au sol, écran vers le haut) correspond maintenant à : sur le côté droit. Inverser les axes y et z signifiant que la position de repos est verticale, écran face à l'utilisateur.

Pour cela, il suffit de modifier sa méthode onSensorChanged ainsi :

1. @Override

2. public void onSensorChanged(SensorEvent event) {

3. // Récupérer les valeurs de l'accélération et du champ magnétique:

4. if (event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) {

5. acceleromterVector=event.values;

6. } elseif (event.sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {

7. magneticVector=event.values;

8. }

10. // Retrouver la matrice de rotation

11. SensorManager.getRotationMatrix(resultMatrix, null, acceleromterVector, magneticVector);

13. // Effectuer le changement de coordonnées en utilisant la méthode remap:

14. float[] outR=newfloat[9];

15. SensorManager.remapCoordinateSystem(resultMatrix, SensorManager.AXIS_Y,

16. SensorManager.AXIS_Z, outR);

18. // Retrouver la matrice d'orientation en lui passant outR et non pas resultMatrix

19. SensorManager.getOrientation(outR, values);

21. // Récuperer les différentes valeurs maintenant corrigées dans ce nouveau système.

22. // l'azimut

23. x =(float) Math.toDegrees(values[0]);

24. // le pitch

25. y = (float) Math.toDegrees(values[1]);

26. // le roll

27. z = (float) Math.toDegrees(values[2]);

28. }

VIII - Le gyroscope

Le gyroscope est un capteur qui calcule la vitesse angulaire de votre téléphone. Pour cela rien de bien magique. Il suffit comme d'habitude avec les capteurs de le créer, s'enregistrer/se désenregistrer en tant qu'écouteur et d'implémenter la méthode onSensorChanged :

1. @Override

2. public void onSensorChanged(SensorEvent event) {

3. // Écouter le changement du gyroscope:

4. if (event.sensor.getType() == Sensor.TYPE_GYROSCOPE) {

5. // La vitesse angulaire autour de chaque axe

6. xGyroscope = event.values[0];

7. yGyroscope = event.values[1];

8. zGyroscope = event.values[2];

9. // Vous pouvez faire quelque chose de ça, mais quoi ?o?

10. // demander le rafraichissement de l'écran.

11. redraw();

12. }

13. }

IX - Le capteur vecteur d'orientation

Ce capteur représente le vecteur de rotation de l'appareil, de la même manière que l'orientation. Ce n'est pas un capteur physique, mais plus un capteur interpolé (comme Gravity). Pour finir, ce capteur peut renvoyer quatre valeurs au lieu de trois.

Un exemple d'utilisation de ce capteur se trouve dans les exemples Google ( http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/os/RotationVectorDemo.html ) et montre comment utiliser ce vecteur pour faire tourner un cube en accord avec le mouvement de l'appareil.

Ce qu'en dit Google :

« Le vecteur de rotation représente l'orientation de l'appareil comme une combinaison d'un angle et d'un axe, dans laquelle l'appareil a pivoté d'un angle ? Autour d'un axe <x, y, z>.

Les trois éléments du vecteur de rotation sont <x*sin(?/2), y*sin(?/2), z*sin(?/2)>, tels que l'ampleur du vecteur de rotation est égale à sin (? / 2) et sa direction est égale à la direction de l'axe de rotation.

Les trois éléments du vecteur de rotation sont égaux aux trois dernières composantes d'un quaternion unitaire <cos (? / 2), x * sin (? / 2), y * sin (? / 2), z * sin (? / 2)>.

Les éléments du vecteur de rotation sont sans unité. Les axes x, y et z sont définis de la même manière que le capteur d'accélération.

Le système de coordonnées de référence est défini comme une base orthonormée directe, où :

- X est défini comme étant le vecteur d'YZ produit (Il est tangentiel au sol à l'emplacement actuel de l'appareil et pointe à peu près vers l'est).

- Y est tangent à la terre à l'emplacement actuel de l'appareil et pointe vers le pôle nord magnétique.

- Z pointe vers le ciel et qui est perpendiculaire au sol.

values[0]: x*sin(?/2)

values[1]: y*sin(?/2)

values[2]: z*sin(?/2)

values[3]: cos(?/2) (optionnel: seulement si value.length = 4) »

Sinon, de la même manière que d'habitude, voilà la méthode onSensorChanged :

1. float[] mRotationMatrix;

3. @Override

4. public void onSensorChanged(SensorEvent event) {

5. // N'écouter que le capteur rotation vector:

6. if (event.sensor.getType() == Sensor.TYPE_ROTATION_VECTOR) {

7. // La valeur angulaire pour chaque axe

8. xRotation = event.values[0];

9. yRotation = event.values[1];

10. zRotation = event.values[2];

11. // La récupération du cosinus

12. if (event.values.length == 4) {

13. cosRotation = event.values[3];

14. }

15. // see

16. // http://developer.android.com/reference/android/hardware/Sensor.html#TYPE_GYROSCOPE

17. // redessiner

18. redraw();

19. //Pour l'uitliser avec en tant que matrice de rotation:

20. //Convertir le vecteur de rotation en une matrice a 4x4 matrix. the matrix

21. //Cette matrice est interprétée par OpenGl comme étant l'inverse

22. //du vecteur de rotation.

23. SensorManager.getRotationMatrixFromVector(mRotationMatrix, event.values);

24. }

25. }

X - Conclusion

Les capteurs nous permettent d'avoir une interaction plus grande avec l'environnement, j'espère que maintenant ils n'ont plus de secret pour vous.

XI - Remerciements

J'adresse ici tous mes remerciements à jacques_jean pour l'excellence de ses corrections orthographiques et Mickael Baron pour la mise au gabarit.

Je remercie Feanorin pour la pertinence de ses remarques, son aide et sa présence.