Robot Mindstorms NXT 2.0

Chapitres traités   

Cette petite étude va nous permettre de réaliser du développement Java sur le système embarqué intégré au Robot Mindstorms NXT 2.0.

Ce robot est livré avec un logiciel de programmation spécifique que nous n'exploiterons pas ici. Il s'agit d'une programmation simple qui s'exploite de façon graphique par ajout de blocs fonctionnels successifs.

Il est plus intéressant de travailler directement sur le pilotage des composants constituant ce robot à l'aide de classes Java adaptées. Pour cela nous devons enlever le petit S.E. interne et en proposer un autre qui intègre du coup une Machine Virtuelle Java avec l'ensemble des classes spécifiques à ce Robot. Il est donc nécessaire de flasher la mémoire interne du Robot avec ce nouveau S.E. compatible Java.

Attention, avant de réaliser cette opération, nous devons impérativement en préalable installer le logiciel fourni avec le Robot afin que les différents drivers soient correctement installés.

  1. Vous disposez d'une documentation générale sur la mise en oeuvre des différents programmes à implementer en Java : LEJOS-EBOOK.
  2. API sur l'ensemble des classes Java relatives au pilotage interne du Robot : API_LEJOS.
  3. API sur l'ensemble des classes Java relatives au pilotage à distance du Robot depuis un PC : PCAPI_LEJOS.

Choix du chapitre Installation du programme LEJOS NXJ

LeJOS NXJ est un environnement de programmation Java pour les Robots LEGO MINDSTORMS NXT. Cela nous permet de programmer ce Robot en Java. Il est constitué :

  1. D'un firmware, qui inclut une Machine Virtuelle Java, que nous devons introduire en lieu et place de celui existant.
  2. D'une librairie de classes Java (classes.jar) qui implémentent toutes les fonctionnalités attendues du Robot (pilotage des moteurs, récupérations des informations des capteurs, etc.). Cette librairie implémente le leJOS NXJ API (Application Programming Interface).
  3. D'un éditeur de lien qui fusionne les classes spécifiques au robot (issues de classes.jar) pour former un fichier binaire qui sera ensuite téléchargé dans le robot et qui pourra donc s'exécuter.
  4. Un ensemble d'outils pour flasher le firmware, télécharger des programmes, debbuger, et bien d'autres fonctions.
  5. Une API PC pour écrire des programmes sur votre PC qui communique avec les programmes leJOS NXJ qui utilisent les flux Java au travers du Bluetooth ou de l'USB ou encore en utilisant le protocole de communication LEGO (LCP).
  6. En ensemble de programmes d'exemple.
Comme nous venons de le dire, le firmware leJOS doit être flasher dans le Robot NXT en remplacement du firmware standard initial LEGO MINDSTORMS. Voici la procédure à suivre :
  1. Récuperer tout d'abord le logiciel leJOS_NXJ_0.85-Setup.exe.
  2. Lancez ce programme pour l'installation :

  3. Bien entendu, pour la suite, il est préférable d'avoir installé la JDK et même de préciser sa localisation en réglant la variable d'environnement PATH :

  4. Si cette variable d'environnement est bien spécifiée, vous devez voir alors la boîte de dialogue suivante. Il s'agit juste ici de valider le début de l'installation :

  5. Vous avez la possibilité de choisir votre répertoire de destination. Remarquez au passage la reconnaissance de laJDK installée :

  6. Vous pouvez également choisir votre répertoire qui va stocker l'ensemble de vos projets :

  7. Une fois que ces différents informations sont collectées, l'installation proprement dite peut débuter :

  8. Si une ancienne version est déjà installée, on vous propose alors de la désinstaller :

  9. Après confirmation, la désinstallation commence :

  10. Losrque la désinstallation est terminée, vous en êtes averti :

  11. La nouvelle version peut maintenant être installée :





  12. Lorsque vous cliquez sur Finish, l'utilitaire qui permet de flasher le firmware NXJ se lance pour vous permettre de mettre à jour éventuellement cette nouvelle version. Si c'est la première fois ou si c'est pour une remise à jour, vous devez cliquer sur le bouton Start program.

  13. Assurez-vous que le robot est disponible et surtout connecté :

  14. On vous demande ensuite d'effacer la totalité de la mémoire du Robot NXT et donc de supprimer tous les fichiers déjà intégrés :

  15. Encore une fois, on vous demande de bien connecter votre Robot NXT avec le cable USB.

  16. Après validation, nous voyons la progression du transfert du firmware avec ses différents fichiers :


  17. On vous demande éventuellement de flasher un autre Robot :

L'utilitaire qui permet de flasher le firmware se nomme nxjflashginst.bat qui se trouve dans le sous-répertoire bin du répertoire d'installation de leJOS NJX.
.

 

Choix du chapitre Compilation, Téléchargement et exécution des programmes

Comme nous le savons, les programmes Java doivent être compilés en fichiers class avant d'être exécutés. Toutefois, pour le système leJOS NXJ, tous ces fichiers class, pour qu'ils puissent être opérationnels dans le Robot NXT, doivent être regroupés ensemble au travers d'un éditeur de lien qui va produire un fichier binaire (avec l'extension .nxj) avant d'être ensuite déployé dans le système embarqué NXT.

La procédure est alors la suivante :

  1. Compilation du programme à l'aide le la commande nxjc :

    nxjc Exemple.java

  2. Edition de lien, déploiement et exécution au travers de la commande nxj :

    nxj -r Exemple

Utilisation des outils de leJOS NXJ en ligne de commande

LeJOS utilise le compilateur standard Java pour compiler chacun des programmes. Cependant, il est nécessaire de remplacer la librairie standard par la version prévue par leJOS qui s'appelle classe.jar. Pour cette raison, une commande spéciale a été introduite pour résoudre cette problématique, il s'agit de la commande nxjc.

Les programmes leJOS NXJ sont différents des programmes standard Java parce qu'ils ne supportent pas le chargement dynamique de classes. L'ensemble de toutes les classes utilisées dans le programme sont collectées ensemble dans un paquet pour former un fichier binaire qui porte l'extension .nxj. Ce processus se réfère à un éditeur de lien spécifique. Le fichier binaire est alors déployé vers le Robot NXT.

Respectivement, les outils pour compiler, lier et déployer les programmes sont les suivants :
  1. nxjc
  2. nxjlink
  3. nxjupload
  4. nxj

Noter que normalement vous avez juste besoin des commandes nxjc ainsi que njx, sachant que cette dernière est l'équivalent de nxjlink suivi de nxjupload.
.

Compilation

nxjc <fichiers java>

Exemple :

nxjc
Exemple.java

nxjc appelle en réalité javac avec les paramètres suivants :

-bootclasspath est proposé parce que leJOS n'utilise pas les classes standard du paquet java.lang mais utilise ses propres versions placées dans l'archive classes.jar.

Edition de lien

nxjlink [-v|--verbose][-g|--debug][-a|--all] classe-principale -o <binary>

Exemple :

nxjlink
-v Exemple -o Exemple.nxj

Sur cet exemple l'éditeur de lien fusionne la classe principale avec d'autres classes annexes présentes dans le projet, notamment celles qui figurent dans l'archive classes.jar de leJOS pour produire un fichier binaire NXJ représentant le programme qui va être déployé et exécuter directement sur la machine.

  1. -v or --verbose : cette option donne la liste en temps réel, d'une part des noms des classes présentes dans le projet avec même la signature de chacune des méthodes internes. Cette option peut être très utile en phase de débuggage.
  2. -g or --debug : cette option lance un moniteur de débuggage intégré au programme qui va être exécuté. Cela permet notamment au programme d'être interrompu dans sa phase d'exécution (appui sur les touches Entrée+Echappe).
  3. -a or --all : Par défaut, l'éditeur de lien efface les méthodes qui ne sont pas utilisées. Avec cette option, vous demandez à inclure systématiquement toutes les méthodes qu'elles soient utilisées ou non. Normalement, ce n'est jamais nécessaire.
  4. -h or --help : permet de connaître l'ensemble des options disponibles dans cette commande.
Déploiement de l'application

nxjupload [-b|--bluetooth][-u|--usb][-d|--address adresse][-n|--name nom][-r|--run] <binary>

Exemple :

nxjupload
Exemple.nxj

Permet de transférer le fichier binaire (.nxj). Par défaut, l'USB est choisi en premier et ensuite le Bluetooth. Si l'option --bluetooth est spécifiée, seulement la connexion Bluetooth est sollicité. Si l'option --usb est choisie, c'est cette fois-ci la connexion USB qui est seulement utilisé.

Lorsque le Bluetooth est utilisé, une recherche du pilote Bluetooth est activée, uniquement si --address est positionné, et que la connexion avec la bonne adresse est possible.

  1. -d or --address : Lorsque le Bluetooth est utilisé, une recherche du pilote Bluetooth est activée, uniquement si --address est positionné, et que la connexion avec la bonne adresse est possible.
  2. -n or --name : ce paramètre permet de limiter la recherche sur le NXT avec le nom donné. Si ce nom n'est pas spécifié, nxjupload essaie de se connecter sur chacun des robot NXT qu'il trouve et déploie le program sur le premier qui s'est correctement connecté.
  3. -r or --run : lorsque cette option est spécifiée, le programme est automatiquement lancé sur le robot dès qu'il a été déployé.
Commande générale

nxj [option] classe-principale

Exemple :

nxj
-r Exemple

La commande nxj réalise l'édition de lien et le déploiement en une seule fois. C'est donc équivalent à la commande njxlink suivie de nxjupload. N'importe quelle options de nxjlink et de nxjupload peut alors être utilisée.

Ainsi, dans l'exemple ci-dessus, grâce à l'option -r, nous demandons que le programme démarre instantanément dès qu'il se trouve dans le robot.
.

Programme sur PC qui pilote le robot à distance

Nous pouvons avoir besoin de développer un programme sur PC qui pilote le robot à distance. Cette fois-ci la notion de déploiement n'existe plus. Par ailleurs, nous n'avons pas besoin des mêmes librairies (pccomm.jar au lieu de classes.jar). Pour finir, comme le PC n'est pas une informatique embarquée, les critères de compilation et d'exécution sont différents. Du coup, il existe deux outils spécifiques pour réaliser la compilation et l'exécution dans le cas d'un programme qui gère le robot tout en restant sur le PC :

  1. nxjpcc : compilateur.
  2. nxjpc : lance le programme.

nxjpcc Exemple.java // compilation
nxjpc Exemple // exécution

 

Choix du chapitre Développement avec NetBeans - premier programme

Après toutes ces explications, nous allons maintenant mettre en oeuvre notre premier programme qui va simplement visualiser un message de bienvenue sur l'afficheur de la brique du Robot. Ce programme est certe très simple, mais il va nous permettre de bien comprendre la procédure à suivre.

Création de la librairie NXT dans Netbeans qui sera utile pour tous les projets

Pour être tranquille dans l'élaboration de tous vos projets concernant le robot NXT, je vous propose de mettre en place une nouvelle bibliothèque, nommée pourquoi pas NXT, qui intègre l'archive classes.jar, puisque nous l'avons vu, est absolument nécessaire pour piloter correctement le robot. Vous avez ci-dessous toute la procédure à suivre.

Si vous devez faire de la communication entre votre PC et le robot, il serait souhaitable également d'intégrer dans votre bibliothèque l'archive pcomm.jar. Vous pouvez d'ailleurs prendre toutes les archives qui vous sont proposées.

Ecriture de notre premier programme

Comme vous avez l'habitude de le faire, construisez un projet classique Java et introduisez le code suivant. N'oubliez pas d'intégrer la nouvelle bibliothèque NXT afin que nous puissions utilisez la classe Button qui fait partie de l'archive classes.jar et qui représente un des boutons de la brique du Robot.

leJOS NXJ supporte la méthode standard System.out.println() et envoie le résultat sur le petit afficheur interne à la brique du Robot NXT. Nous rajoutons dans ce programme la classe Button qui se trouve dans le paquetage lejos.nxj. Cette classe dispose de la méthode waitForPress() qui est bloquante et qui attend que l'utilisateur actionne l'un des boutons de la brique.

Compilation, déploiement et exécution du programme sur le robot

Nous allons maintenant utiliser les outils de compilation et d'exécution propre à leJOS NXJ. Voici la procédure à suivre :

  1. Au niveau de Netbeans, sauvegarder votre fichier source :

  2. A l'aide du mode console, placez-vous dans le bon répertoire, c'est-à-dire dans le répertoire source de votre projet, compilez à l'aide de la commande nxjc, déployez et exécutez votre programme à l'aide de la commande nxj -r :

Nous venons de découvrir comment mettre en oeuvre un projet pour le Robot NXT. Nous allons maintenant nous occuper de connaître l'ensemble des classes qui vont nous permettre de piloter correctement le robot, comme le pilotage des différents moteurs, la prise en compte des différents capteurs, etc.

 

Choix du chapitre Les entrées-sorties et les capteurs

Ce chapitre va nous permettre de rentrer dans le vif du sujet. Cette partie nous renseigne sur les classes spécialisées sur les entrées-sorties physiques ainsi que les différents capteurs utilisés dans le Robot NXT. Ce que nous entendons par entrées-sorties concerne plus spécifiquement ce qui se trouve sur la brique du Robot, les boutons poussoir pour les entrées, l'afficheur LCD et un tout petit haut-parleur pour les sorties.

LCD

La classe LCD représente l'afficheur LCD du Robot NXT, et comme il est unique, aucune instance n'a été créée. Du coup, toutes les méthodes de cette classe sont statiques. Par contre cette classe est capable de travailler en mode texte ou en mode graphique.

LCD en mode texte

En mode texte, l'afficheur LCD propose 16 caractères sur 8 lignes. Pour écrire un texte, vous devez préciser les coordonnées sous la forme (x, y) tel que cela vous est montré ci-dessous. Attention, nous commençons toujours par le zéro. Ainsi, x va de 0 à 15, alors que y va de 0 à 7.

Méthodes d'écriture en mode texte de la classe LCD représentant l'afficheur LCD du ROBOT
void drawString(String texte, int x, int y)
Affiche la chaîne de caractères spécifiée en paramètre sur l'afficheur du Robot au niveau de la ligne et de la colonne données par les coordonnées (x, y).
void drawInt(int entier, int x, int y)
Affiche la valeur entière spécifiée en paramètre sur l'afficheur du Robot au niveau de la ligne et de la colonne données par les coordonnées (x, y). L'entier est aligné à gauche et prend le nombre de caractères requis pour afficher la totalité du nombre.
void drawInt(int entier, int nombre, int x, int y)
Variante de la méthode précédente qui utilise systématiquement le nombre de caractères spécifié en paramètre pour afficher la valeur entière. Cela permet d'une part de proposer une justification à droite du nombre, et d'autre part, lorsque nous effectuons une boucle dans notre programme, nous sommes sûr que l'ancienne valeur est systématiquement effacer par l'écriture de la nouvelle valeur.
void clear()
Efface le contenu de l'afficheur, ce qui permet d'avoir un afficheur LCD vierge de toute information.
void setAutoRefresh(int mode)
Par défaut, l'afficheur LCD se rafraîchi automatiquement. Si vous désirez contrôler ce rafraîchissement pour le proposer quand vous le désirez, vous pouvez désactiver cet auto-rafraîchissement en proposant la valeur 0 à l'argument de cette méthode.
void refresh()
Permet de faire une demande de rafraîchissement manuelle de l'afficheur LCD en supposant que le rafraîchissement automatique ait été désactivé.
A titre d'exemple, je vous propose de réaliser le programme suivant qui permet d'afficher la mémoire disponible du Robot :
package robot;

import lejos.nxt.*;

public class TestAfficheur {
   public static void main(String[] args)  throws Exception {
       LCD.drawString("Mem libre:", 0, 0);
       LCD.drawInt((int) System.getRuntime().freeMemory(), 6, 10, 0);
       Thread.sleep(5000);
   }
}

Nous avons découvert préalablement que nous pouvions également écrire sur l'afficheur à l'aide de la méthode toute simple System.out.println(). Le problème, c'est que dans ce cas là, l'affichage se décale automatiquement lorsque toutes les lignes sont déjà complètes, et nous perdons ainsi un certain nombre d'informations.

LCD en mode graphique

En mode graphique, cette fois-ci l'afficheur LCD propose 100 pixels de largeur sur 64 pixels de hauteur. Nous retrouvons par contre le même système de coordonnées qu'en mode texte : (x, y). Attention, nous commençons toujours par le zéro : soit x de 0 à 99, et y de 0 à 63.

Méthodes en mode graphique de la classe LCD représentant l'afficheur LCD du ROBOT
void drawString(String texte, int x, int y)
Affiche la chaîne de caractères spécifiée en paramètre sur l'afficheur du Robot au pixel près suivant les coordonnées (x, y) proposées.
void drawString(String texte, int x, int y, boolean inverser)
Une variante de la méthode précédente qui permet d'inerser les couleurs, texte en blanc sur fond noir.
void drawChar(char c, int x, int y, boolean inverser)
Cette méthode affiche un seul caractère sur l'afficheur du Robot au pixel près suivant les coordonnées (x, y) proposées.
void setPixel(int rvb, int x, int y)
Affiche ou efface le pixel spécifié par les coordonnées. rvb = 1, le pixel est positionné, rvb = 0, le pixel n'est plus visible.
void drawLine(int x0, int y0, int x1, int y1)
Trace une ligne qui part des coordonnées (x0, y0) vers les coordonnées (x1, y1).
void drawRect(int x, int y, int largeur, int hauteur)
Trace un rectangle dont le placement du bord haut gauche est spécifié par les coordonnées (x, y) avec les dimensions proposées en argument.
void drawRoundRect(int x, int y, int largeur, int hauteur, int largeurArc, int hauteurArc)
Trace un rectangle à bords arrondis dont le placement est spécifié par les coordonnées (x, y) avec les dimensions proposées en argument. L'arrondi est traité par les deux derniers paramètres.
void fillRect(int x, int y, int largeur, int hauteur)
Trace un rectangle plein dont le placement du bord haut gauche est spécifié par les coordonnées (x, y) avec les dimensions proposées en argument.
A titre d'exemple, je vous propose de réaliser le programme suivant qui permet d'effectuer quelques tracés particuliers sur l'afficheur du Robot :
package robot;

import javax.microedition.lcdui.Graphics; // Attention, il faut importer le contexte graphique correspondant à l'afficheur du robot

public class LCDGraphique {
   public static void main(String[] args)  throws Exception {
       Graphics g = new Graphics();
       g.drawLine(5, 5, 60, 60);
       g.drawRect(62, 10, 25, 35);
       g.drawRoundRect(62, 10, 25, 35, 25, 35);
       g.drawString("Bienvenue", 15, 30, true);
       Thread.sleep(5000);
   }
}

Boutons de commande de la brique

Sur la brique, vous disposez de quatre boutons qui permettent de commander le robot, comme le choix du programme à lancer par exemple. Il est possible d'interagir directement avec l'un de ces boutons. Il existe pour cela la classe Button qui représente l'ensemble des boutons présents sur la brique. Cette classe possède également quatre attributs statiques qui implémentent chaucn des boutons spécifiques :

  1. Button.ENTER : bouton orange.
  2. Button.ESCAPE : bouton sous le bouton orange.
  3. Button.LEFT : bouton flèche gauche.
  4. Button.RIGHT : bouton flèche droite.
Méthodes de la classe Button représentant les différents boutons de la brique du ROBOT
boolean isPressed()
Teste si le bouton spécifié est actionné.
void waitForPressAndRealease()
Attente de l'action et du relâchement du bouton spécifié ou d'un bouton quelconque.
void waitForPress()
Attente de l'action uniquement (dès l'appui) du bouton spécifié ou d'un bouton quelconque.
int readButtons()
Retourne la valeur correspondant à la somme des codes des boutons qui sont actionnées actuellement.
Bouton ENTER LEFT RIGHT ESCAPE
Code 1 2 4 8
A titre d'exemple, je vous propose de réaliser le programme suivant qui permet d'afficher un texte sur l'afficheur du Robot suivant le bouton sélectionné :
package robot;

import lejos.nxt.*;

public class Boutons {
   public static void main(String[] args)  throws Exception {
       LCD.drawString("Boutons ?", 0, 0);
       while (true) {
           if (Button.ENTER.isPressed()) LCD.drawString("Validation", 0, 0);
           if (Button.LEFT.isPressed())    LCD.drawString("Gauche    ", 0, 0);
           if (Button.RIGHT.isPressed()) LCD.drawString("Droite      ", 0, 0);
           if (Button.ESCAPE.isPressed()) break; 
       }
       LCD.drawString("Quitter  ?  ", 0, 0);
       Button.ENTER.waitForPressAndRelease();
   }
}

 

Appliquer des sons

Il existe un petit haut-parleur dans la brique qui permet d'appliquer des sons. La classe Sound implémente cet haut-parleur qui ne possède pas d'instance et dont toutes les méthodes sont du coup statiques.

Méthodes de la classe Sound qui agissent sur le haut-parleur du ROBOT
void playTone(int fréquence, int durée)
Applique la fréquence spécifiée pendant un certain temps sur le haut-parleur du Robot.
void beep()
Applique un son bref sur le haut-parleur du Robot.
void twoBeep()
Applique deux sons bref consécutifs sur le haut-parleur du Robot.
void beepSequence()
Applique un arpège descendant sur le haut-parleur du Robot.
void beepSequenceUp()
Applique un arpège ascendant sur le haut-parleur du Robot.
void buzz()
Applique un son réduit mais relativement long sur le haut-parleur du Robot.
void pause(int durée)
Vous pouvez appliquer cette méthode à chaque fois que vous désirez attendre, afin d'éviter de placer un bloc try-catch qui est nécessaire lorsque nous prenons à la place la méthode Thread.sleep().
void playSample(File fichier)
Joue une musique pré-enregistrée sur le haut-parleur du Robot.
void playSample(File fichier, int volume)
Joue une musique pré-enregistrée sur le haut-parleur du Robot avec un volume spécifique.
void playNote(int[] enveloppe, int fréquence, int durée)
Le tableau enveloppe représente la courbe d'évolution de la note avec respectivement l'attaque, la diminusion, le soutien et le relâchement de la note suivant la fréquence spécifiée et la durée proposée. Heureusement, des constantes prédéfinies sont déjà créées, comme FLUTE, PIANO et XYLOPHONE, mais rien n'empêche de faire vos propres expérimentations.
A titre d'exemple, je vous propose de réaliser le programme suivant qui permet de jouer quelques sons sur le Robot :
package robot;

import lejos.nxt.*;

public class JouerSons {
    private static final int[] notes = {2349, 115, 0, 5, 1760, 165, 0, 35};

   public static void main(String[] args) {
       for (int i=0; i<notes.length; i+=2) {
           int temps = notes[i+1]*10;
           int note = notes[i];
           if (note != 0) Sound.playTone(note, temps);
           Sound.pause(temps);
       }
       Sound.beepSequence();
       Sound.beepSequenceUp();
   }
}

Les capteurs

Le Robot NXT est fourni avec quatre capteurs. LeJOS propose une abstraction logicielle, au travers de classes spécifiques qui encapsulent l'ensemble des fonctionnalités prévues, de ces différents types de capteurs.

  1. Un capteur de fin de course (simple contact),
  2. Un capteur de son,
  3. Un capteur de lumière,
  4. Un capteur ultrason.
Le capteur physique doit être connecté à ce que nous appelons un port. Ainsi, l'objet logiciel représentant le capteur désiré doit connaître sur quel port il est connecté. Afin de fournir la bonne information concernant cette connexion, vous devez créer une instance du capteur à l'aide du constructeur correspondant. Comme il existe 4 ports d'entrée, la classe spécifique SensorPort représentant l'ensemble de ces ports possède une instance pour chacun d'eux :
  1. SensorPort.S1,
  2. SensorPort.S2,
  3. SensorPort.S3,
  4. SensorPort.S4.

Méthodes du capteur de fin de course du ROBOT réprésenté par la classe TouchSensor
TouchSensor(SensorPort port)
Constructeur qui permet d'associer le port utilisé par le capteur physique de type fin de course.
boolean isPressed()
Détermine si le capteur est actuellement activé ou relâché.
A titre d'exemple, je vous propose de réaliser un tout petit programme qui teste le capteur de fin de course du Robot :
package robot;

import lejos.nxt.*;

public class FinDeCourse {
   public static void main(String[] args) throws InterruptedException {
       TouchSensor capteur = new TouchSensor(SensorPort.S3);
       LCD.drawString("Attente !", 0, 0);
       while (!capteur.isPressed());
       LCD.drawString("Capteur Actif", 0, 0);
       Thread.sleep(5000);
   }
}

Capteur ultrason

Ce capteur peut travailler suivant deux modes, de façon continue (comportement par défaut) où par demande explicite (ping). Dans le mode par défaut, le capteur est continuellement opérationnel, vous pouvez ainsi estimer la distance du robot par rapport à l'obstacle quand vous le désirez au travers de la méthode getDistance().

Méthodes du capteur ultrason du ROBOT réprésenté par la classe UltrasonicSensor
UltrasonicSensor(SensorPort port)
Constructeur qui permet d'associer le port utilisé par le capteur physique ultrason.
int getDistance()
Donne la distance en cm entre l'obstacle et le capteur du robot. Si aucun echo n'est détecté, la valeur retournée est alors 255. Toutefois, la portée maximale est de 170 cm.
void ping()
Au travers de cette méthode, nous sortons du mode continu. Avec une seule demande, 8 echos sont alors capturés. Ces echos peuvent ensuite être exploités à l'aide de la méthode suivante readDistances(). Cette méthode permet de faire une valeur moyenne des valeurs retournées afin d'éviter des echos intempestifs.
void getDistances(int[] distances)
Fournit un tableau de valeurs entières correspondant aux distances évaluées. Attention, un délai approximativement de 20 ms est requis entre l'appel de la méthode ping() et de la méthode getDistances(). Malheureusement, ce délai n'est pas inclus dans la méthode. Faire appel à cette méthode avant l'écoulement de cette période provoque une erreur et aucune valeur n'est alors retournée.
int getDistance()
Cette méthode est également disponible à l'issu de l'appel de la méthode ping() en retournant le tout premier écho.
int continuous()
Lorsque nous appelons la méthode ping() le mode continue se trouve automatiquement et définitivment désactivé. Pour le remettre en place, vous devez faire appel à cette méthode continuous().
A titre d'exemple, je vous propose de réaliser un tout petit programme qui donne continuellement la distance du Robot par rapport à un obstacle (relativement gros) :
package robot;

import lejos.nxt.*;

public class CapteurUltraSon  {
   
   public static void main(String[] args) {
       UltrasonicSensor capteur = new UltrasonicSensor(SensorPort.S1);
       
       LCD.clear();
       LCD.drawString(capteur.getVersion(), 0, 0);
       LCD.drawString(capteur.getProductID(), 0, 1);
       LCD.drawString(capteur.getSensorType(), 0, 2);
       LCD.drawString("Distance :    cm", 0, 3);
       while (!Button.ESCAPE.isPressed())    LCD.drawInt(capteur.getDistance(), 3, 10, 3);

   }
}

 

Choix du chapitre Piloter le Robot à l'aide de ses moteurs

Il existe la classe Motor qui représente la partie logicielle d'un des moteurs du Robot. Pour être utilisé correctement, chaque moteur doit être connecté sur l'un des ports prévus à cet effet. Ils sont référencés par les lettres A, B et C. Comme pour les capteurs, la classe Motor instancie un moteur par port. Ainsi, nous avons les objets :

  1. Motor.A
  2. Motor.B
  3. Motor.C
Cette classe est pourvue d'un ensemble de méthodes qui permettent de piloter le moteur concerné, et de savoir à tout moment ce qu'il fait. Comme il existe la classe SensorPort qui représente les ports d'entrée, il existe également la classe MotorPort qui représente les ports de sortie :
  1. MotorPort.A,
  2. MotorPort.B,
  3. MotorPort.C.

La classe Motor propose deux modes de fonctionnement, soit en mode régulation soit en accélération progressive. La régulation contrôle en temps réel la vitesse de moteur à l'aide du tachymètre afin que la vitesse proposée soit parfaitement maîtriser. Avec l'accélération progressive, la classe corrige la vitesse pour atteindre la valeur désirée progressivement.

Méthodes de la classe Motor représentant un des moteurs physiques du ROBOT
Motor(MotorPort port)
Constructeur qui permet d'associer le port utilisé par le moteur physique.
void forward()
Démarre le moteur vers l'avant. Sauf avis contraire, le moteur ne s'arrête plus et continue sur sa lancée.
void backward()
Démarre le moteur vers l'arrière. Sauf avis contraire, le moteur ne s'arrête plus et continue sur sa lancée.
void reverseDirection()
Inverse la direction actuelle du moteur.
void stop()
Arrête la rotation du moteur quelque soit son sens.
void flt()
Arrête l'alimentation du moteur qui s'arrête donc mais de façon moins abrupte que la méthode stop().
int getTachoCount()
Chaque moteur est pourvu d'un tachymètre qui mesure la rotation effective. Cette méthode retourne l'angle de rotation du moteur en dégré.
void resetTachoCount()
Remet à zéro la valeur du tachymètre.
void setSpeed(int vitesse)
Change la vitesse de rotation du moteur en degré par seconde.
int getRotationSpeed()
Retourne la valeur actuelle de la vitesse de rotation du moteur en degré par seconde.
int getSpeed()
Retourne la valeur courrante de la vitesse de rotation du moteur en degré par seconde.
void rotate(int angle)
Fait tourner le moteur suivant un angle spécifique en degré par rapport à la position actuelle. Il s'agit d'un déplacement de tant de degrés.
void rotate(int angle, boolean immédiat)
Même méthode que la précédente mais avec la possibilté d'être non blocante (retour immédiat) si immédiat est à true.
void rotateTo(int angle)
Il s'agit ici de se positionner suivant une valeur absolue de l'angle (il s'agit d'une position et non d'un déplacement relatif). Il faut faire tourner le moteur de telle sorte que l'angle spécifié soit atteint. Si l'axe du moteur se trouve déjà sur cette valeur, le moteur ne bouge pas. Si l'axe du moteurs se trouve à l'opposer, le moteur tourne avec un angle de 180°.
void rotateTo(int angle, boolean immédiat)
Même méthode que la précédente mais avec la possibilté d'être non blocante (retour immédiat) si immédiat est à true.
boolean isMoving()
Permet de savoir si le moteur est toujours en mouvement.
void regulateSpeed(boolean modeRégulation)
Active la régulation de vitesse sur le moteur (valeur par défaut).
void lock()
Bloque la rotation du moteur. Si le robot se trouve sur une pente relativement importante, il est possible qu'à l'arrêt, il continue à bouger par simple gravité. Cette méthode est là pour palier ce problème.
Voici une liste d'exemples qui illustrent les possibilités intéressantes de l'ensemble de ces méthodes :

Piloter les deux moteurs avec des méthodes basiques
  1. Faire tourner les deux moteurs du robot vers l'avant pendant une seconde.
  2. Faire tourner les deux moteurs du robot vers l'arrière pendant une seconde.
  3. Arrêter les deux moteurs.
package robot;

import lejos.nxt.*;

public class Moteurs  {
   
   public static void main(String[] args) {
       Motor gauche = new Motor(MotorPort.C);
       Motor.A.forward();
       gauche.forward();
       Sound.pause(1000);
       Motor.A.backward();
       gauche.reverseDirection();
       Sound.pause(1000);
       Motor.A.stop();
       gauche.stop();
   }
}
Utilisation du tachymètre

Les moteurs du Robot NXT sont construits avec un tachymètre intégré qui permet de contrôler en temps réel la valeur de l'angle en cours, exprimé en degré, de l'axe du moteur.

  1. Faire tourner le moteur de droite du robot vers l'avant.
  2. Attendre jusqu'à ce que l'axe du moteurs ait fait un tour complet.
  3. Arrêter le moteur.
  4. Attendre que le moteur se soit arrêté.
  5. Afficher la valeur du tachymètre sur l'afficheur du robot.
  6. Changer la vitesse de rotation du moteur à une valeur de 200 (360 par défaut - un tour complet par seconde).
  7. Remettre à zéro le tachymètre.
  8. Répéter une autre fois les étapes de 1 à 5.
package robot;

import lejos.nxt.*;

public class Moteurs  {
   private static Motor droite = Motor.A;
   
   public static void main(String[] args) {
        rotate360Degres();
        droite.resetTachoCount();
        droite.setSpeed(200);
        rotate360Degres();
        LCD.drawString("Quitter ?", 0, 0);
        Button.waitForPress();
   }

   public static void rotate360Degres() {
        droite.forward();
        int nombre = 0;
        while (nombre < 360) nombre = droite.getTachoCount();
        droite.flt();
        LCD.drawInt(nombre , 0, 1);
        while(droite.getRotationSpeed()>0);
        LCD.drawInt(droite.getTachoCount(), 7, 1);
   }
}
Contrôle la position de l'axe du moteur avec précision

La classe Motor implémente un thread de régulation qui fonctionne constamment. Comme nous l'avons déjà évoqué, cette classe utilise deux principes, dont l'un d'entre eux permet d'arrêter le moteur suivant un angle bien déterminé.

  1. Faire tourner le moteur de droite du robot un tour complet.
  2. Afficher la valeur du tachymètre sur l'afficheur du robot.
  3. Faire tourner le moteur pour revenir à l'origine (angle = 0);
  4. Changer la vitesse de rotation du moteur à une valeur de 200 (360 par défaut - un tour complet par seconde).
  5. Répéter une autre fois les étapes de 1 à 3.
package robot;

import lejos.nxt.*;

public class Moteurs  {
    private Motor droite = Motor.A;
   
    public static void main(String args[]) {
        LCD.drawString("Rotation", 0, 0);
        Moteurs robot = new Moteurs();
        robot.lancer();
    }

    private void lancer() {
        rotate360Degres();
        droite.setSpeed(200);
        rotate360Degres();
    }

    private void rotate360Degres() {
        droite.rotate(360);
        LCD.drawInt(droite.getTachoCount(), 4, 0, 1);
        droite.rotateTo(0);
        LCD.drawInt(droite.getTachoCount(), 4, 0, 2);
    }
}
Arrêter la rotation du moteur

Quelquefois, vous pouvez avoir besoin d'arrêter le moteur en cours de route avant que ce dernier n'atteigne l'angle requis. Nous allons voir comment procéder.

  1. Faire tourner le moteur de droite du robot deux tours complets.
  2. Tant que le moteur tourne, affichez la valeur indiquée par le tachymètre.
  3. Lorsqu'un des boutons est appuyé, arrêter immédiatement le moteur.
  4. Lorsque le moteur est complètement arrêté, afficher la valeur du tachymètre.
  5. Attendre l'action sur un bouton quelconque.
  6. Faire tourner le moteur pour revenir à l'origine (angle = 0);
  7. Répéter une autre fois les étapes de 2 à 5.
package robot;

import lejos.nxt.*;

public class Moteurs  {
    private Motor droite = Motor.A;
   
    private void lancer() {
        System.out.println("Interrupt  \n rotation");
        Sound.twoBeeps();
        Button.waitForPress();
        droite.rotate(720, true);
        visualiserRotation(1);
        droite.rotateTo(0, true);
        visualiserRotation(1);
    }

     public void visualiserRotation(int ligne) {
        while(droite.isMoving()) {
            LCD.drawInt(droite.getTachoCount(), 4, 0, ligne);
            if(Button.readButtons()>0) droite.stop();
        }
        while(droite.getRotationSpeed()>0);
        LCD.drawInt(droite.getTachoCount(), 4, 8, ligne);
        Button.waitForPress();
    }

    public static void main(String[] args) {
        new Moteurs().lancer();
    }
}

Remarquez qu'en fonctionnement, si nous appuyons sur un bouton de la brique avant que la rotation du moteur soit accomplie, le moteur s'arrête bien instentanément sans faire le tour complet. Autrement, la méthode stop() n'a aucun effet.

 

Choix du chapitre Gestion événementielle

LeJOS implémente la gestion événementielle sur deux types d'éléments physiques, les touches présentes sur la brique et les ports d'entrées où se connectent les différents capteurs. Cela permet de prendre en compte une intervention de la part de l'utilisateur et d'autre part une évolution (un changement) sur les capteurs d'entrée.

Cette technique est très intéressante puisque un programme principal peut fonctionner en tâche de fond en toute liberté, et lorsqu'un événement spécifique se produit, comme l'appui sur le capteur de fin de course, un thread séparé se lance alors, au travers des méthodes prévues par la gestion événementielle, qui va peut être demander d'arrêter la progression du robot.

Gestion événementielle pour contrôler l'appui sur un des boutons de la brique du Robot - ButtonListener
void buttonPressed(Button bouton)
Cette méthode est sollicité dès que l'utilisateur appui sur le bouton désigné.
void buttonReleased(Button bouton)
Cette méthode est sollicité dès que l'utilisateur relâche le bouton désigné.
Gestion événementielle pour contrôler le changement de valeur d'un des ports d'entrée du Robot - ButtonListener
void stateChanged(SensorPort portEntrée, int ancienneValeur, int nouvelleValeur)
Cette méthode est sollicité dès que le port spécifié change de valeur, donc dès que le capteur correspondant enregistre une évolution.
Petit test qui permet d'afficher sur l'écran du LCD les événements produit par le bouton de validation et le capteur de fin de course
package robot;

import lejos.nxt.*;

public class Evenements implements ButtonListener, SensorPortListener {
    private Evenements() {
       Button.ENTER.addButtonListener(this);
       SensorPort.S3.addSensorPortListener(this);
    }

    public static void main(String[] args) {
        new Evenements();
        Button.ESCAPE.waitForPressAndRelease();
    }

    public void buttonPressed(Button button) {
        LCD.drawString("Validation...", 0, 0);
    }

    public void buttonReleased(Button button) {
        LCD.clear();
    }

    public void stateChanged(SensorPort sp, int ancien, int nouveau) {
        LCD.drawString("Capteur :", 0, 1);
        LCD.drawInt(nouveau, 4, 9, 1);  // 181 (actif)  -- 1023 (relâché)
    }
}

 

Choix du chapitre Communication à distance

LeJOS supporte également les communications à distance eu travers du Bluetooth où de la connexion USB. Des classes sont spécialement prévues pour implémenter la communication en général, en prenant en compte les deux types de support. Par ailleurs, nous pouvons utiliser les flux Java qui offrent une très grande souplesse d'utilisation.

L'USB offre l'avantage de la vitesse, mais nécessite le cable entre le PC et le robot. Le Bluetooth est moins rapide, mais offre plus de souplesse d'utilisation. Quelque soit votre choix, vous pouvez prétendre à différents types de communication :

  1. D'un robot vers un autre robot,
  2. De l'ordinateur vers le robot,
  3. D'un téléphone portable vers le robot,
  4. etc.

Pour pouvoir communiquer :

  1. La première chose que vous devez prendre en compte et l'établissement de la connexion. Une connexion, bien entendu, doit avoir un récepteur et un émetteur.
  2. D'une part, le récepteur attends une connexion demandée par un émetteur alors que l'émetteur tente de se connecter à un récepteur spécifique qui lui doit justement attendre la connexion.
  3. Une fois que la connexion est établie, les deux parties, ensemble, doivent ouvrir des flux d'entrées-sorties afin d'écrire et de lire les informations requises.

NXT Récepteur

Un programme qui gère la réception d'une communication à distance dans le robot se fait au travers de la méthode waitForConnection() d'une classe spécifique pour le Bluetooth ou pour le port USB :

  1. Bluetooth.waitForConnection() : renvoie un objet de type BTConnection.
  2. USB.waitForConnection() : renvoie un objet de type USBConnection.
Ces deux classes implémentent l'interface NXTConnection. Voici les deux manières d'établir une connexion suivant le cas :

NXTConnection connexion = Bluetooth.waitForConnection();
ou
NXTConnection connexion = USB.waitForConnection();

Méthodes utiles de l'interface lejos.nxt.comm.NXTConnection
void openInputStream()
Renvoie un flux d'entrée représentant un flux d'octets non interprétés.
void openOutputStream()
Renvoie un flux de sortie représentant un flux d'octets non interprétés.
void openDataInputStream()
Renvoie un flux d'entrée de type DataInputStream qui permet de récupérer des données primitives comme les entiers, les réels, etc.
void openDataOutputStream()
Renvoie un flux de sortie de type DataOutputStream qui permet d'envoyer des données primitives comme les entiers, les réels, etc.
void close()
Clôture la communication.

Nous connaissons bien les classes DataInputStream et DataOutputStream avec lesquelles nous pouvons utiliser les méthodes de lecture et d'écriture, comme readInt(), readDouble(), etc. respectivement avec writeInt(), writeDouble(), etc.

Emission côté PC

Côté PC, nous pouvons également proposer également la communication à distance, nous avons la même démarche que précédemment avec toutefois une classe différente, qui se nomme NXTConnector qui est capable d'établir les deux types de connexion, par Bluetooth ou par USB.

Voici par exemple la demande de connexion avec le robot au travers du Bluetooth avec le numéro de pin = 1234 :

NXTConnector connexion = new NXTConnector();
connexion.connectTo(1234); // pin par défaut du robot NXT

Méthodes utiles de la classe lejos.pc.comm.NXTConnector
void getInputStream()
Renvoie un flux d'entrée représentant un flux d'octets non interprétés.
void getOutputStream()
Renvoie un flux de sortie représentant un flux d'octets non interprétés.
void getDataIn()
Renvoie un flux d'entrée de type DataInputStream qui permet de récupérer des données primitives comme les entiers, les réels, etc.
void openDataOut()
Renvoie un flux de sortie de type DataOutputStream qui permet d'envoyer des données primitives comme les entiers, les réels, etc.
void close()
Clôture la communication.
void connectTo()
Etablie la connexion avec le robot NXT au dessus de tout protocole en mode paquets.
void connectTo(int mode)
Etablie la connexion avec le robot NXT en spécifiant le mode souhaité connect(1234).
void connectTo(String url)
Etablie la connexion suivant l'URL proposée connect("usb://").
void connectTo(String url, int mode)
Etablie la connexion suivant l'URL proposée et dans le mode souhaité.

Pensez bien à utiliser les outils de compilation et d'exécution, respectivement njxpcc et njxpc.
.

Exemple de programmes de communication entre le PC et le robot NXT

Vous avez ci-dessous un petit exemple de communication entre le PC et le Robot NXT. Le programme émetteur (côté PC) envoie un nombre. Le récepteur (le robot) récupère ce nombre, double sa valeur et le renvoie au PC.

Emetteur - Communiquer avec le robot depuis le PC
package robot;

import java.io.*;
import lejos.pc.comm.*;

public class Emetteur {
   public static void main(String[] args) throws NXTCommException {
       NXTConnector  connexion = new NXTConnector();
       if (connexion.connectTo(1234)) {  // Code PIN du Robot NXT
           DataOutputStream requete = connexion.getDataOut();
           DataInputStream reponse = connexion.getDataIn();
            try {
                requete.writeInt(15);
                requete.flush();
                System.out.println("NXT : "+reponse.readInt());
                requete.close();
                reponse.close();
                connexion.close();
                System.out.println("Arret du programme...");
            }
            catch (IOException ex) {
                System.err.println("Problème pour envoyer les valeurs");
            }
       }
       else System.err.println("Aucune connexion Bluetooth");
   }
}
Récepteur - Attente de connexion du Robot et traitement de l'information le cas échéant
package robot;

import java.io.*;
import lejos.nxt.*;
import lejos.nxt.comm.*;

public class Recepteur implements ButtonListener {
   public Recepteur() {
       Button.ESCAPE.addButtonListener(this);
       LCD.drawString("Attente...", 0, 0);
       NXTConnection connexion = Bluetooth.waitForConnection();
       DataInputStream requete = connexion.openDataInputStream();
       DataOutputStream reponse = connexion.openDataOutputStream();
       LCD.drawString("Connexion", 0, 0);
        try {
            int nombre = requete.readInt();
            LCD.drawInt(nombre, 0, 1);
            reponse.writeInt(nombre*2);
            reponse.flush();
            requete.close();
            reponse.close();
            connexion.close();
        } 
        catch (IOException ex) {
            LCD.drawString("Probleme", 0, 3);
        }
       Sound.pause(4000);
   }
   
   public static void main(String[] args) { 
       new Recepteur();
   }

    public void buttonPressed(Button button) {
       System.exit(0); 
    }

    public void buttonReleased(Button button) {
        
    }
}

 

Choix du chapitre Travaux pratiques - Piloter le robot

Maintenant que nous connaissons les éléments qui constituent le robot, nous allons pouvoir réaliser nos premiers Tps de pilotage du robot. Nous allons progresser lentement pour bien maîtriser les mouvements et les capteurs du robot dans leurs globalités.

Faire avancer et faire tourner le robot

Le premier TP nous permet d'utiliser les deux moteurs du robot ainsi que les quatres boutons présents sur la brique. Nous allons commencer par quelques mouvements simples. Voici les fonctionnalités attendues une fois que le programme est en cours d'exécution :

  1. Un appui sur le bouton de validation : demande l'avance du robot droit devant lui à la vitesse par défaut.
  2. Un appui sur le bouton gauche : le robot continu à avancer en tournant sur la gauche de 30° environ.
  3. Un appui sur le bouton droite : le robot continu à avancer en tournant sur la droite de 30° environ.
  4. Un appui sur le bouton d'annulation : le programme s'arrête.
Pour élaborer ce projet, vous aller tenir compte d'un certain nombre de critères :
  1. La gestion des boutons doit se faire de façon événementielle.
  2. Vous allez mettre en oeuvre deux classes, Piloter la classe principale de l'application et la classe Robot qui représente les fonctionnalités du robot.
  3. La classe Robot possède deux moteurs, droite et gauche, peut avancer(), tournerADroite() et tournerAGauche().
Cas d'utilisation

Diagramme de classe

Diagramme de séquence

Correction : Sources du projet.
.

Pouvoir arrêter le robot

Le deuxième TP est une variante du premier. Il serait intéressant de pouvoir arrêter les mouvements du robot sans pour cela quitter impérativement le programme. Je propose donc de nous reservir du bouton de validation pour arrêter le robot. Ainsi le bouton de validation devient l'équivalent d'une bascule :

  1. Si le robot est à l'arrêt, l'appui sur le bouton permet de le faire avancer.
  2. Un nouvel appui sur ce bouton le robot s'arrête.
Pour élaborer ce projet, vous aller tenir compte d'un certain nombre de critères :
  1. La gestion des boutons doit toujours se faire de façon événementielle.
  2. Vous allez conserver les deux classes, Piloter la classe principale de l'application et la classe Robot qui représente les fonctionnalités du robot.
  3. La classe Robot possède deux moteurs, droite et gauche, peut avancer(), arrêter(), tournerADroite() et tournerAGauche().
Cas d'utilisation

Diagramme de classes

Diagramme de séquence

Correction : Sources du projet.
.

Détection d'un obstacle

Lorsque le robot s'approche d'un obstacle comme le mur de la salle par exemple, il serait souhaitable de le faire ralentir dans un premier temps et de l'arrêter ensuite au contact du mur. Il pourrait alors reculer légèrement pour le dégager du mur.

Pour tester cette fonctionnalité, je vous propose dans un premier temps de ne plus vous préoccuper de faire tourner le robot à droite ou à gauche ou même de faire la demande de l'arrêt. Tenez-vous en uniquement à l'avance du robot avec précausion. Voici d'ailleurs la procédure à suivre :

  1. Un appui sur le bouton de validation : demande l'avance du robot droit devant lui à la vitesse par défaut et affichage de la distance sur l'écran LCD.
  2. Lorsque le robot arrive à 40 cm de l'obstacle : demande de ralentir avec une vitesse de 200.
  3. Le robot atteint l'obstacle : le robot s'arrête et fait une marche arrière d'un tour de roue.
Pour élaborer ce projet, vous aller tenir compte d'un certain nombre de critères :
  1. La gestion du bouton de validation se fait de façon événementielle.
  2. La classe Robot possède deux attributs supplémentaires qui correspondent aux deux capteurs ultrason et finDeCourse. Nous rajoutons également des méthodes qui vont correspondre au fonctionnement attendu savoir, une nouvelle méthode avancer(vitesse), avec aussi les méthodes avancerPrudemment() et reculerLegerement().
Cas d'utilisation

Diagramme de classes

Diagramme de séquence

Correction : Sources du projet.
.

Ralentir progressivement

Nous allons reprendre toutes les fonctionnalités précédentes en rajoutant juste un ralentissement progressif au lieu de proposer une vitesse de ralentissement unique à l'approche de l'obstacle. Nous en profiterons également pour prévoir un demi-tour complet du robot après qu'il ait reculé de l'obstacle.

  1. Un appui sur le bouton de validation : demande l'avance du robot droit devant lui à la vitesse par défaut et affichage de la distance sur l'écran LCD.
  2. Lorsque le robot arrive à 60 cm de l'obstacle : demande de ralentir progressivement suivant la distance parcourue.
  3. Lorsque le robot arrive à 30 cm de l'obstacle : maintenir une vitesse d'avance de 120.
  4. Le robot atteint l'obstacle : le robot s'arrête, fait une marche arrière d'un tour de roue et effectue ensuite un demi-tour complet pour être prêt à repartir dans le sens inverse.
Pour élaborer ce projet, vous aller tenir compte d'un certain nombre de critères :
  1. La gestion du bouton de validation se fait de façon événementielle.
  2. Vous devez changer le comportement de la méthode avancerPrudemment() de la classe Robot afin de correspondre aux nouvelles fonctionnalités. Par ailleurs, vous devez implémenter une nouvelle méthode qui se nomme demiTour().
Cas d'utilisation

Diagramme de classes

Diagramme de séquence

Correction : Sources du projet.
.

Tous les mouvements avec détection d'obstacle - Gestion des threads

Nous allons faire une fusion de tous les TPs précédents. Nous désirons ainsi que notre robot puisse avancer, tourner à gauche et à droite, s'arrêter. Toutefois, lorsque le robot en en mouvement, il faudrait "en même temps" qu'il soit capable de détecter les obstacles et de réagir en conséquence, c'est-à-dire de ralentir progressivement, faire demi-tour en cas de contact et de repartir comme si de rien n'était.

  1. Un appui sur le bouton de validation : demande l'avance du robot droit devant lui avec une vitesse compatible avec les obstacles éventuels.
  2. Un appui sur le bouton gauche : le robot continu à avancer en tournant sur la gauche de 30° environ.
  3. Un appui sur le bouton droite : le robot continu à avancer en tournant sur la droite de 30° environ.
  4. Un appui sur le bouton d'annulation : le programme s'arrête.
  5. Lorsque Le robot atteint l'obstacle : le robot s'arrête, fait une marche arrière d'un tour de roue et effectue ensuite un demi-tour complet pour être prêt à repartir dans le sens inverse.
  6. Lorsque le demi-tour est accompli : le robot continu d'avancer à une vitesse compatible avec les obstacles éventuels.
Pour élaborer ce projet, vous aller tenir compte d'un certain nombre de critères :
  1. La gestion des trois boutons se fait de façon événementielle.
  2. Vous allez conserver les deux classes, Piloter la classe principale de l'application et la classe Robot qui représente les fonctionnalités du robot.
  3. Vous pouvez vous servir des attributs et des méthodes que vous avez mis en oeuvre dans les TPs précédents. Pour les méthodes, il faudra peut-être en modifier le contenu.
  4. En tout cas, l'avance du robot doit se réaliser dans une tâche concurrente pour prendre en compte toutes les actions souhaitées par l'utilisateur.
Cas d'utilisation

Diagramme de classes

Diagramme de séquence complet

Correction : Sources du projet.
.

Pilotage du robot à distance

Nous finissons notre étude en reprenant exactement les fonctionnalités précédentes, mais le robot cette fois-ci sera piloté à distance et non plus par les boutons de la brique.

  1. Un appui sur le bouton Avancer : demande l'avance du robot droit devant lui avec une vitesse compatible avec les obstacles éventuels.
  2. Un appui sur le bouton Gauche : le robot continu à avancer en tournant sur la gauche de 30° environ.
  3. Un appui sur le bouton Droite : le robot continu à avancer en tournant sur la droite de 30° environ.
  4. Un appui sur le bouton Arreter : le robot s'arrête.
  5. Unappui sur le bouton Quitter : la connexion avec le robot s'interromp.
Pour élaborer ce projet, vous aller tenir compte d'un certain nombre de critères :
  1. Cette fois-ci, nous avons deux composants logiciels, un côté PC et un autre sur l'informatique embarquée du robot.
  2. Côté robot, pour quitter le programme définitivement, une gestion événementielle est prévue avec le bouton d'annulation.
  3. Toujours côté robot, vous allez conserver les deux classes, Piloter la classe principale de l'application et la classe Robot qui représente les fonctionnalités du robot.
  4. Aucune modification n'ait à apporter sur cette denière classe Robot.
  5. Par contre, côté PC, vous devez implémenter la classe Distante qui permettra, au travers d'une IHM succinte, de commander le robot par Bluetooth.
Cas d'utilisation

Diagramme de composants

Diagramme de classes

Diagramme de séquence

Correction : Sources du projet.
.

Choix du chapitre Commandes directes par bluetooth

A titre de dernier exemple, je vous propose de voir comment piloter le robot par liaison bluetooth. Effectivement, il existe une possibilité qui consiste à envoyer des commandes directes qui sont instantanément interprétées par ce dernier. Dans ce cas de figure, vous n'avez pas besoin de déployer un programme spécifique à l'intérieur même du robot. Cette fonctionnalité est directement intégré dans la brique NXT, quelque soit d'ailleurs le firmware intallé.

  1. Voici d'ailleurs le document PDF concernant les commandes autorisées par les robot mindstorms NXT.
  2. Ainsi que le document propre à la commande du Capteur Ultrasonic.
Nous allons élaborer un projet qui permet de se connecter à un robot spécifique, au travers de son adresse MAC. Une fois que la connexion avec le robot est établie, il est possible de soumettre ensuite la requête bluetooth souhaitée sous forme de chaîne de caractères composée d'une suite de valeurs hexadécimales séparée par un espace :

La première valeur hexadécimale de votre requête bluetooth indique la taille de la réponse. Si vous ne souhaitez pas de réponse, il suffit de placer le code hexadécimal 00.

Cette application fait également office de service qui permet de récupérer la requête bluetooth souhaitée, sous forme également de chaîne de caractères avec la structure que nous venons de voir, et de la soumettre instantanément au robot actuellement connecté. Là aussi, si une réponse est démandée, une chaîne de caractères est renvoyée au client dans le même format.

Vous avez ci-dessous le code Java correspondant. Attention toutefois, vous devez passer par la commande nxjpc afin de permettre l'exécution d'une application sur le PC qui pilote le robot à distance nxjpc nxtbluetooth/NXTBluetooth.

nxtbluetooth.NXTBluetooth.java
package nxtbluetooth;

import java.awt.*;
import java.awt.event.ActionEvent;
import java.io.*;
import java.net.*;
import java.util.Scanner;
import lejos.pc.comm.*;
import javax.swing.*;

public class NXTBluetooth extends JFrame {
    private JTextArea notification= new JTextArea("Connectez-vous !\n", 20, 50);
    private JTextField iRobot = new JTextField("00:16:53:0C:BC:BF");
    private JTextField iRequete = new JTextField("03 03 F4 01 F4 01");
    private JToolBar boutons = new JToolBar();
    private NXTComm bluetooth;
    private boolean ouvert = false;
    private String retour;
    private ServerSocket service;

    private NXTBluetooth() {
        setTitle("Robot NXT");
        notification.setBackground(Color.YELLOW);
        iRobot.setBackground(Color.RED);
        iRequete.setBackground(Color.ORANGE);
        add(boutons, BorderLayout.NORTH);
        boutons.add(iRobot);
        boutons.add(new AbstractAction("Bluetooth") {
            @Override
            public void actionPerformed(ActionEvent e) {
                connexion();
            }
        });
        boutons.add(new AbstractAction("Stop") {
            @Override
            public void actionPerformed(ActionEvent e) {
                deconnexion();
            }
        });
        boutons.add(iRequete);
        boutons.add(new AbstractAction("Soumettre") {
            @Override
            public void actionPerformed(ActionEvent e) {
                commande();
            }
        });
        boutons.add(new AbstractAction("Effacer") {
            @Override
            public void actionPerformed(ActionEvent e) {
                notification.setText("");
            }
        });        
        add(new JScrollPane(notification));
        pack();
        setDefaultCloseOperation(EXIT_ON_CLOSE);
        setVisible(true);
    }
    
    private void connexion() {
        try {
            if (!ouvert) {
                NXTInfo infoBluetooth = new NXTInfo(NXTCommFactory.BLUETOOTH, "NXT", iRobot.getText());
                bluetooth = NXTCommFactory.createNXTComm(NXTCommFactory.BLUETOOTH);      
                ouvert = bluetooth.open(infoBluetooth);
                if (ouvert) { 
                    notification.append("Communication avec le robot "+iRobot.getText());
                    new Thread(new Serveur()).start();
                }
                else notification.append("ATTENTION problème avec "+iRobot.getText());
                notification.append("\n");    
            }
        } 
        catch (NXTCommException ex) {
            notification.append("ATTENTION probleme de BLUETOOTH...\n");
        }
    }
    
    private void deconnexion() {
       if (ouvert) 
       try {
          bluetooth.close();
          ouvert = false;
          notification.append("BLUETOOTH interrompu...\n");
          if (service!=null) {
              service.close();
              notification.append("Service (5588) interrompu...\n");
          }
       } 
       catch (IOException ex) { }     
    }
    
    private void commande() {
        try {
            if (ouvert) {
                String[] hexa = iRequete.getText().split("\\s");
                byte[] octets = new byte[hexa.length];
                for (int i=0; i<octets.length; i++) octets[i] = (byte) Integer.parseInt(hexa[i], 16);  
                byte nombreRetour = octets[0];    
                if (nombreRetour>0) {
                    octets[0] = 0;
                    byte[] reponse = bluetooth.sendRequest(octets, nombreRetour);
                    notification.append("Commande : ("+iRequete.getText()+") -- Retour : (");
                    StringBuilder chaine = new StringBuilder();
                    for (byte octet : reponse) chaine.append(octet+" ");
                    chaine.deleteCharAt(chaine.length()-1);
                    retour = chaine.toString();
                    notification.append(retour+")\n");
                }
                else {
                    octets[0] = (byte) 0x80;
                    bluetooth.sendRequest(octets, 0);
                    retour = "";
                    notification.append("Commande : ("+iRequete.getText()+")\n");
                }
            }
            else notification.append("ROBOT non connecté...\n");
        } 
        catch (Exception ex) {
            notification.append("ATTENTION : Commande non valide pour le ROBOT\n");
        }
    }

    private class Serveur implements Runnable {
        @Override
        public void run() {
            try {
                service = new ServerSocket(5588);
                notification.append("Service (5588) en action...\n");
                while (ouvert) {
                   notification.append("Attente d'un nouveau client...\n");
                   Socket client = service.accept();
                   String adresse = client.getInetAddress().getHostAddress();
                   notification.append("----------------------------- Connexion : "+adresse+" : \n");
                   Scanner requete = new Scanner(client.getInputStream());
                   PrintWriter reponse = new PrintWriter(client.getOutputStream(), true);
                   boolean fin = false;
                   do {
                       String soumission = requete.nextLine();
                       fin = soumission.equalsIgnoreCase("fin");
                       if (!fin) {
                           iRequete.setText(soumission);
                           commande();
                           reponse.println(retour);
                       }
                   } 
                   while(!fin);
                   client.close();
                   notification.append("----------------------------- Deconnexion\n");
                }
            }
            catch (IOException ex) {
                notification.append("Perte de la communication...\n");
            }            
        }        
    }
    
    public static void main(String[] args) throws Exception {
        new NXTBluetooth();
    }
}