Introduction Dans ce projet, la tâche qui m’a été confiée était de développer sur un poste distant une Interface Homme-Machine (IHM) de contrôle et de test des différentes fonctionnalités du robot, telles que : L’asservissement numérique de position et de vitesse d'un moteur à courant continu L’asservissement de position et de vitesse d'un moteur pas-à-pas Le contrôle d'une caméra intelligente Le pilotage d'un télémetre Infra-Rouge Le pilotage d'une récepteur-démodulateur Infra-Rouge Le pilotage d'un modulateur-émetteur Infra-Rouge Chacun de ces modules correspond à un ensemble de classes développées en C++ par les six autres étudiants de ce projet. Choix logiciels (langage et environnement de développement) Dans un premier temps, le choix du format de développement de l’IHM s’orientait vers MFC (Microsoft Foundation Class) sous Visual Studio 6.0, ou à l’aide des librairies Qt sous linux , avec une programmation en C++ dans les deux cas. L’application aurait donc été stockée sur un poste distant, avec une connexion au robot à l’aide du serveur web embarqué, en l’occurrence BOA, package de µClinux (version embarquée de linux), système d’exploitation utilisé sur les cartes ColdFire. L’utilisation du CGI aurait donc été nécessaire pour permettre la communication entre les programmes fonctionnant sur le robot et l’IHM. La Common Gateway Interface est une technologie standard implémentée sur tous les serveurs Web. Elle permet l'échange de données (dans les deux sens) entre un programme de votre cru et le contenu d'une page Web visualisée par un internaute distant. Par extension, on appelle aussi "CGI" les programmes utilisant cette interface. Un tel programme peut être écrit à l'aide de n'importe quel langage de programmation : les plus utilisés sont C, Perl ou encore python. A la différence d'un script Java, le programme CGI sera exécuté sur la machine hébergeant le serveur Web, et non sur la machine du client. Mais cette méthode impliquait un choix de système d’exploitation unique, et une application stockée hors du robot, nécessitant de la transporter séparément et de l’installer avant chaque utilisation. Java s’est donc imposé comme langage nécessaire à ce projet, puisqu’ étant multiplateformes, et portable si l’on utilise la méthode des applets stockées sur le robot et téléchargées chez le client par l’intermédiaire du serveur web. Une fois l’applet téléchargée, celle-ci communique avec les programmes exécutés sur le robot via le réseau TCP/IP câblé (puis sans-fil WIFI par la suite), par l’intermédiaire d’une socket, sorte de conduit qui permet la communication entre deux processus, ceux-ci pouvant être actifs sur des machines différentes. Le Java Mis au point en 1991 par la firme Sun Microsystems, Java est un langage orienté objet reprenant les caractéristiques principales du C++, le rendant moins encombrant et plus portable, et éliminant ses points difficiles, ses caractéristiques critiques, telles que : les pointeurs (permettant d’écrire n’importe où en mémoire et de faire des dégâts) la surcharge d'opérateurs l'héritage multiple la libération de mémoire (qui est transparente pour l'utilisateur, il n'est plus nécessaire de créer de destructeurs) Il intègre de plus une meilleure gestion des erreurs. Les chaînes et les tableaux sont désormais des objets faisant partie intégrante du langage. Toutefois Java est beaucoup moins rapide que le langage C++, il perd en rapidité ce qu'il gagne en portabilité… Fichier source, compilation et machine virtuelle Le fichier source d'un programme écrit en Java est un simple fichier texte dont l'extension est par convention .java. Ce fichier source doit être un fichier texte non formaté, c'est-à-dire un fichier texte contenant uniquement les caractères ASCII de base. Contrairement aux langages compilés traditionnels, pour lesquels le compilateur crée un fichier binaire directement exécutable par un processeur donné (c'est-à-dire un fichier binaire contenant des instructions spécifiques à un processeur), le code source Java est compilé en un langage intermédiaire (appelé pseudo-code ou bytecode) dans un fichier portant le même nom que le fichier source à l'exception de son extension (.class). Compilation traditionnelle : Fichier source Compilation Fichier compilé sous Linux Fichier compilé sous Windows Fichier compilé sous UNIX Exécution Système Linux Compilation Java : Système Windows Système UNIX Fichier source Précompilation Pseudo code Machine virtuelle Linux Compilation Exécution Machine virtuelle Windows Machine virtuelle UNIX Exécution Système Linux Système Windows Système UNIX Cette caractéristique est majeure, car c'est elle qui fait qu'un programme écrit en Java est portable, c'est-à-dire qu'il ne dépend pas d'une plate-forme donnée. En réalité le code intermédiaire n'est exécutable sur aucune plate-forme sans la présence d'une machine virtuelle, un interpréteur (la machine virtuelle est d'ailleurs parfois appelée interpréteur Java) tournant sur une plate-forme donnée, et capable d'interpréter le code intermédiaire. Pour effectuer ces compilations, il est nécessaire de disposer du Java Development Kit. Le JDK est le kit de développement de base que propose gratuitement la firme Sun Microsystem. Téléchargeable sur le site de cette dernière (http://java.sun.com), le Kit de développement comprend plusieurs outils, parmi lesquels: javac: le compilateur Java java: un interpréteur d'applications (machine virtuelle) applet viewer: un interpréteur d'applets jdb: un débogueur javap: un décompilateur, pour revenir du bytecode au code source javadoc: un générateur de documentation jar: un compresseur de classes Java Applications et Applets Java permet de créer deux types de programmes: Les applications : programmes tels qu'on les connaît, c'est-à-dire s'exécutant dans le système d'exploitation à condition d'avoir installé une machine virtuelle. Les applets (prononcez Applettes, et traduisez Appliquettes en français) : il s'agit de petites applications destinées à fonctionner sur un navigateur. Ainsi une applet a un champ d'action beaucoup plus réduit qu'une application pour des raisons de sécurité (une applet ne peut par exemple pas accéder au système sur lequel elle s'exécute...). Voici donc le code d'une applet toute simple: import java.awt.*; public class FirstApplet extends java.applet.Applet { public void init (){ add(new Label("Hello World")); } } Le programme devrait en toute logique afficher le message "Hello World" à l'écran sur le navigateur. Les sockets Il s'agit d'un modèle permettant la communication inter processus (IPC - Inter Process Communication) afin de permettre à divers processus de communiquer aussi bien sur une même machine qu'à travers un réseau TCP/IP. La communication par socket est souvent comparée aux communications humaines. On distingue ainsi deux modes de communication: Le mode connecté (comparable à une communication téléphonique), utilisant le protocole TCP. Dans ce mode de communication, une connexion durable est établie entre les deux processus, de telle façon que l'adresse de destination n'est pas nécessaire à chaque envoi de données. De plus, si un paquet se perd il sera automatiquement retransmis. Malheureusement cela engendre une perte de temps. Le mode non connecté (analogue à une communication par courrier), utilisant le protocole UDP. Ce mode nécessite l'adresse de destination à chaque envoi, et aucun accusé de réception n'est donné. Ainsi, on communique plus vite car c’est un protocole plus léger (utilisé en streaming vidéo, voix sur IP …). Leur représentation dans le modèle OSI se situe juste au-dessus de la couche transport (protocoles UDP ou TCP), elle-même utilisant les services de la couche réseau (protocole IP / ARP). Modèle des sockets Application utilisant les sockets UDP/TCP IP/ARP Ethernet, ... Modèle OSI 7 Application 6 Présentation 5 Session 4 Transport 3 Réseau 2 Liaison 1 Physique Comme dans le cas de l'ouverture d'un fichier, la communication par socket utilise un descripteur pour désigner la connexion sur laquelle on envoie ou reçoit les données. Ainsi la première opération à effectuer consiste à appeler une fonction créant un socket et retournant un descripteur (un entier) identifiant de manière unique la connexion. Ainsi ce descripteur est passé en paramètres des fonctions permettant d'envoyer ou recevoir des informations à travers la socket. Voici le schéma d'une communication en mode connecté (mode utilisé dans notre cas) : Serveur Pour implémenter les sockets en C/C++, nous utilisons des fonctions et des structures disponibles dans la librairie <sys/socket.h> : socket() Client bind() listen() socket() accept() connect() •Socket() : création de la socket •Bind() : lier à une adresse et un port •Listen() : en attente de connexion •Accept() : accepte la connexion •Connect() : établit une connexion avec un serveur •Recv() : lecture dans la socket •Send() : écriture dans la socket recv() send() send() recv() •Close() : fermeture de la socket Environnement de développement Pour réaliser l’interface et le code nécessaire à son fonctionnement, je me suis aidé de l’ environnement de développement NetBeans, un outil pour écrire, compiler, déboguer et déployer des programmes, dans n’importe quel langage, dont Java – l’utilisation du JDK se fait donc implicitement. Etant lui-même écrit en Java, son utilisation est identique sous Windows et sous Linux. Cela m’a donc offert une liberté supplémentaire, puisque je pouvais facilement m’adapter aux systèmes d’exploitations dont je disposais. Pour le développement des programmes fonctionnant sur les cartes ColdFire (à l’intérieur du robot), le langage étant le C++ et le compilateur étant spécifique au système d’exploitation µClinux, je me servais d’un simple bloc-notes et d’une console pour compiler. Analyse UML Diagramme de déploiement Robot Moteurs CC Carte coldfire <<//>> T CC Client 1 Applet client 1 Moteurs PP T PP <<//>> image.jpg Client 2 Caméra <<Sockets>> <<RS 232>> Applet client 2 T Cam Télémètres Concentrateur <<TCP/IP>> <<TCP/IP>> Sockets <<//>> Serveur T Telem Réception IR <<//>> T Rec/Dem Client N Applet client N T Mod/Emi Modules me concernant <<//>> Emission IR Cas d’utilisation <<actor>> Tester moteurs CC Module CC <<actor>> Tester moteurs P/P Module PP Tester caméras Module caméra <<actor>> <<actor>> Module télémètres Tester télémètres Testeur <<actor>> Tester récepteur/dem IR Module R/D IR Tester émetteur/mod IR Module E/M IR <<actor>> Diagramme de séquence Testeur Interface :Module CC :Module PP :Module caméra :Module télémètre :Module R/D IR :Module E/M IR Entre infos test Click sur Go! Lance test Affiche état mot Retour. état mot. Click sur stop Lance ordre stop Affiche état mot Retour. état mot. Lance mot. Stop mot. Click sur action Lance action Affiche état mot Retourne état moteur Effectue action Capture ou Flux Demande image Affiche image Retourne image Capture image Demande sauvegarde Confirme Sauvegarde Click mesurer Demande mesure Affiche mesures Retourne mesures Mesure Entre code Click envoie! Envoie code Emet code Affiche code Retourne code Démodule code Module code L’Interface Homme Machine La partie servant à connecter les applet à l’aide de socket est interfacée de la même manière sur chacune d’entre elles. Située dans la partie inférieure, cette partie demande deux renseignements : le numéro de la carte à laquelle se connecter et le port du serveur de socket. Module moteurs Ce module concerne l’étudiant 1 et 2, puisque les données nécessaires pour piloter les moteurs sont identiques. La distinction se fera donc au niveau du code, par l’analyse du paramètre « type », renseigné par la balise <PARAM> contenue entre les balises <APPLET> et </APPLET> dans le fichier html intégrant l’applet. <PARAM NAME="type" VALUE="CC"> Les différents onglets, « haut niveau » et « bas niveau » permettent un contrôle différent selon l’état d’avancement des modules. Module caméra Ce module concerne l’étudiant 3 pilotant la caméra. Une fois connectée, l’applet offre la possibilité de capturer une image et de l’afficher dans la partie supérieure. Elle permet aussi, par l’intermédiaire du menu déroulant situé sur la droite, de choisir de détecter le vert ou le rouge (selon la couleur des quilles) présent actuellement devant la caméra. Celle-ci renverra donc les coordonnées d’un rectangle encadrant la zone où cette couleur est majoritairement présente. Module télémètres Ce module concerne l’étudiant 4 chargé d’effectuer des mesures à l’aide des télémètres. Dans l’onglet « Tous », c’est l’intégralité des télémètres, situés tout autour du robot sur deux niveaux, qui sera interrogée, renvoyant une suite de valeurs en millimètres aussitôt affichée aux emplacements prévus. L’onglet « Unique » quant à lui sert à tester un télémètre à la fois, puisqu’ avant de gérer tous les télémètres simultanément, l’étudiant 4 s’est concentré sur la commande d’un seul. Module réception/démodulation IR Ce module concerne l’étudiant 6, renvoyant la position et la direction de tous les robots présents sur le plateau de jeu. En effet, lorsque l’applet est connectée à la carte, une pression sur le bouton « Capturer » va demander au module réception/démodulation IR d’effectuer des calculs à l’aide des informations de position émises par les balises, qu’il reçoit par l’intermédiaire des récepteurs. Derrière l’IHM Pour faire communiquer cette interface avec les modules développés par chaque étudiant, pilotant les éléments physiques du robot, une couche logicielle supplémentaire est nécessaire. Située sur le robot, son rôle est d’attendre les connexions entrantes, pour les rediriger vers les modules interrogés. Pour se faire, la connexion entrante est attendue par un « serveur de rendez-vous », serveur de socket ne faisant qu’attendre le client pour le rediriger. Mais, pour permettre plusieurs connexions simultanées au robot, il est nécessaire de dédoubler le processus de rendez-vous une fois la connexion établie. Permettant la communication avec le client dans le processus « fils », le processus « père » termine cette communication avant de se remettre en attente d’une nouvelle connexion. Ainsi, plusieurs clients peuvent communiquer avec le robot, sans avoir à attendre qu’une communication précédente ne s’achève. Le programme est donc multitâches et réentrant. Serveur de rendez-vous enum enumtype{CC,PP,CAM,TLM, REC,EMI}; Début Nb d’arguments >2 exécution du code lié au module : CC : des moteurs a courant continu PP : des moteurs pas à pas CAM : des caméras TLM : des télémètres REC : des récépteur/démodulateur IR EMI : des modulateur/émetteur IR OUI NON Fils Père Création de la socket Test du type Attente de connexion Exécution du code correspondant Fermeture de la connexion Réception du type du fils à créer Execl du programme avec arguments Fermeture de la connexion Fin Le premier test « Nb d’arguments >2 » ne sert qu’en cas de simulation, dans le cas où l’étudiant n’aurait pas encore de module à tester. Pour ne pas créer autant de fichiers exécutables que de modules à tester, j’ai préféré intégrer le code – servant à simuler les modules – dans le même programme, précisant en argument le module à simuler lors de son exécution, et entrant donc directement dans la zone de code adéquat (à l’aide de l’instruction switch()). Protocole de communication Afin de normaliser les échanges de données entre l’interface et les modules, j’ai créé différentes classes, dont CMessage, la classe de base de laquelle dérivent toutes les autres. Le but de ces classes est de générer des trames, dont j’ai pu choisir librement le format. Une fois les données membres renseignées, l’utilisateur n’a plus qu’à envoyer dans la socket la trame que lui retourne la fonction membre prévue à cet effet. CMessageCCbn CMessageCChn CMessagePPbn CMessagePPhn CMessageCamImg CMessage CMessageCamCouleur CMessageTelemTous CMessageTelemUnique CMessageRecepteur CMessageEmetteur Les trames ont toute la même syntaxe, seules leurs données et leurs tailles diffèrent : 1er octet 2e octet XX XXXXXX XXXX XXXX Flag Type Taille des (de 0 à 21) données à suivre (en octets) 3e octet … XXXX XXXX XXXX… Données Seule une trame générée par la classe de base CMessage diffère syntaxiquement, puisqu’elle ne possède pas de taille de données, ses données étant facultatives et se limitant à un seul octet, situé après le premier octet. Cette trame n’est donc constituée que de 2 octets. XX XXXXXX XXXX XXXX Flag Type (de 0 à 21) Octet d’information (facultatif) Flag : 0 : trame unique 1 : début de « multi-trame » 2 : suite de « multi-trame » 3 : fin de « multi-trame » (« multi-trame » = trame découpée en plusieurs petites car trop longue pour être envoyée en une seule) Ex : trame unique, de type 15, comportant 3 octets de données 00 001111 0000 0011 Type : 0 : Fin de connexion 2 : (I) Moteur CC bas niveau 4 : (I) Moteur CC haut niveau 6 : (I) Moteur PP bas niveau 8 : (I) Moteur PP haut niveau 10 : (I) Caméra image 12 : (I) Caméra couleur 14 : (I) Télémètre unique distance 16 : (I) Télémètres distances 18 : (I) Récepteur robot 20 : (I) Emetteur code 0010 1010 1100 1001 0101 1111 1 : Connexion 3 : (R) Moteur CC bas niveau 5 : (R) Moteur CC haut niveau 7 : (R) Moteur PP bas niveau 9 : (R) Moteur PP haut niveau 11 : (R) Caméra image 13 : (R) Caméra couleur 15 : (R) Télémètre unique distance 17 : (R) Télémètres distances 19 : (R) Récepteur robot 21 : (R) Emetteur code (I) : message « Impulsion » : utilisé comme ordre ou requête simple. C’est une instanciation de la classe de base CMessage pouvant comporter 1 octet d’information sur l’ordre ou la requête si nécessaire. (R) : message « Renseigné » : utilisé comme retour de données en réponse à une requête, ou comme ordre renseigné dans le cas d’instructions avec paramètres. C’est une instanciation de la classe dérivée adéquate. Contraintes Ecrire un programme pour uClinux équivaut à l’écrire pour linux, à quelques exceptions près. Ainsi, de petits inconvénients comme l’absence du terme « const » fréquent en C++, mais aussi plus important comme l’absence de la méthode fork(), ont impliqués quelques changements de méthodes de programmation. fork() / vfork() La primitive fork() permet de créer un nouveau processus par duplication du processus appelant. Ce qui signifie que le nouveau processus exécute une copie du programme mis en œuvre par le processus père. La primitive fork renvoie un entier qui correspond au numéro du pid du processus créé qui hérite d’un certain nombre d’attributs du processus père : Même programme Mêmes données Arguments et environnement identique Répertoire courant identique Priorité, descripteurs de fichiers, ressources, gestion de signaux identiques… fork() = duplication du processus : même programme, mêmes données ... main() vfork() Père en attente Fils Reprise du père exec() ou exit() Mais uClinux ne fournit pas d’implémentation complète de fork(), mais une implémentation simplifiée, celle du vfork() de BSD (système d’exploitation de la famille UNIX). Avec vfork(), l’exécution du processus père est suspendue jusqu’à ce que le processus fils n’utilise la primitive exec() ou exit(). Dans la plupart des cas l’utilisation de vfork() n’affecte pas le déroulement de programmes pour UNIX ou Linux. Néanmoins, il est important de prendre en considération que le processus fils partage, avec le processus père, la même pile – zone mémoire – et les variables globales. Il est donc important que le processus fils ne revienne pas (return) de la fonction dans laquelle vfork() a été invoqué, n’appelle pas d’autre fonction, ou ne modifie pas de variables du processus père avant d’avoir invoqué avec succès la méthode exit() ou une routine de la famille exec(). Conclusion Ce projet m’a beaucoup appris, tant au niveau technique que méthodologique. En effet l’aspect technique fût très enrichissant. Ayant opté pour certains choix logiciels convenant mieux à nos besoins, j’ai eu l’opportunité d’apprendre un nouveau langage : le Java. De plus, cette aventure m’a fait comprendre le fonctionnement d’un robot, impliquant une réflexion permanente, tant au niveau de sa réalisation matérielle que logicielle (déplacement, perception, stratégie…). Enfin j’ai pu découvrir de nombreuses techniques et autres avancées technologiques utilisées en robotique en général. Aussi, j’ai pu améliorer ma méthodologie, puisqu’un projet de ce type a pour but de nous apprendre à travailler en équipe, mais aussi à être plus performant individuellement, sortant du cadre d’un simple TP. Par ailleurs, j’ai réalisé qu’un groupe ayant un objectif sans directives précises peut difficilement être productif. C’est ainsi que j’ai pris conscience du comportement à adopter dans ce type de projet, où réactivité, investissement et concentration sont des notions capitales.