Développement pour la barrière NetPark

Chapitres traités   

Cette série de TP permet de faire de la programmation pour une informatique dite "embarquée". En réalité, les TPs que je vous propose, piloterons systématiquement la barrière NetPark ou NetRep à distance et sous Linux.

Effectivement, les barrières disposent toute d'un service (développé en Java) qui attend les ordres reçus par les différents clients (programmes que vous aurez à développer) qui sera automatiquement exécuté, bien entendu si le protocole prévu est totalement respecté.

 

Choix du chapitre Description générale des barrières

Dans nos locaux, nous disposons de deux systèmes de gestion de parking : la véritable barrière (DECMA-PARK), ainsi qu'une réplique en modèle réduit (DECMA-REP) qui vous est présentée ci-dessous, et même une version simulée (DECMA-SIM) :

Organisation globale d'un point de vue réseau de Dekma-Park, Dekma-Rep et Dekma-Sim

DECMA-REP  s’intègre dans un parking appelé ‘NetPark’. Dans un parking, il peut exister plusieurs barrières. Un PC est alors distinct des autres : C’est le PC qui contient la base de données, spécifiant entre autre le nombre de places disponibles, et le serveur Web permettant la gestion et le test des bornes du parking à l'aide d'un navigateur. Ce PC se nomme "PC SUPERVISEUR". C'est la logique normale de fonctionnement.

Dans vos TP, ce PC SUPERVISEUR de gestion normale n'existera pas, c'est votre programme avec un comportement bien spécifique qui jouera ce rôle. Dans un premier temps, seule une partie de la barrière sera exploité. Mais par la suite, nous retrouverons un comportement plus logique en rapport avec une barrière de parking.

DECMA-REP

Vous avez, ci-dessous, le synoptique général, avec les différents éléments importants de la maquette DECMA-REP, représentant la véritable barrière physique :

Carte TINI - Point de vue logiciel

Chaque système de barrière dispose d'un fonctionnement autonome avec un comportement spécifique qu'il est possible de régler à sa guise. Chaque barrière possède donc une informatique embarquée qui est une simple petite carte, appelée carte TINI. Malgré sa petitesse, cette carte dispose de la même architecture classique d'un ordinateur plus conséquent. Elle dispose en effet, de son propre processeur, de la mémoire, d'une possibilité de communication avec l'extérieur et d'un système d'exploitation pour gérer tout cela. Cette carte dispose effectivement d'un système embarqué linux.

Généralement, les systèmes embarqués disposent d'un système d'exploitation allégé. C'est tout-à-fait normal. Si nous prenions un vrai système d'exploitation complet, il faudrait alors beaucoup plus de ressources matérielles (notamment plus de mémoire) pour une utilisation plutôt occasionnelles. En effet, une fois que le comportement désiré est mis au point, la carte fonctionne en autonomie sans plus aucune intervention extérieure (mise à part pour une maintenance éventuelle).

Vous remarquez la présence d'une machine virtuelle java directement intégrée dans cette carte TINI. Nous pouvons donc faire du développement Java. Le service que vous allez utiliser pour vos TPs est effectivement écrit en Java et gère l'ensemble du matériel de la barrière connecté par le bus I2C. Ce programme s'appelle, tout simplement, service.

Carte TINI - Côté réseau

Il ne faut pas se leurer, malgré qu'il s'agit d'une simple carte, cette dernière dispose d'un bon nombre de service (comme la plupart des systèmes embarqués). Ce qui est d'ailleurs logique, sinon il nous serait impossible de l'atteindre et de la gérer.

Notamment, nous utiliserons presque systématiquement le sevice Telnet pour contrôler et administrer le fonctionnement du programme service (développé en Java). S'il n'est pas présent, vous téléchargerez (déploirez) ce programme au moyen du service prévu à cet effet, à savoir le service FTP. Si un disfonctionnement réseau apparaît, il est également possible, comme très souvent, de faire appel au service PING.

Adresses IP des Bornes d'accès de cahcune des barrières

... Dekma-Rep : 172.20.3.77
... Dekma-Park : 172.20.3.73

Test de fonctionnement de l’interface de détection d’un véhicule

Analyse d’un passage, ici une sortie :

 

Choix du chapitre Lancement du service dans la barrière

Tous les TPs que vous allez mettre en oeuvre seront situés sur vos postes clients. Vous les développerez en C++ sous environnement Linux. Pour que vos TPs soient opérationnels dans la barrière, il est nécessaire d'avoir un service annexe intermédiaire, placé à l'intérieur même de la barrière, et qui prend en compte l'ensemble des actions désirées. Ce service annexe, comme je l'ai déjà précisé, s'appelle service.

Tous vos applicatifs (en C++) resterons sur votre PC. Vous communiquerez ainsi vos informations à distance au travers du réseau. L'échange s'établit au travers du protocole TCP/IP et l'appel du service demandé (en Java) qui gère la barrière se fait par le numéro 5588.

Remarquez bien au passage que les programmes, d'un côté comme de l'autre, peuvent être écrits dans différents langages. Le tout, c'est que vous repectiez le protocole de communication dont nous découvrirons la teneur dans le chapitre suivant.

Téléchargement du service

La première chose à faire est de contrôler la présence de ce logiciel service sur votre barrière au travers de telnet (voir la rubrique suivante). Si ce n'est pas le cas, il faut télécharger ce service par FTP. Tout d'abord, il faut vérifier la présence de votre service dans le répertoire Logiciels :

Ensuite, au moyen d'une console "Terminal" :

  1. Placez-vous sur ce répertoire Logiciels,
  2. Connectez-vous au service FTP de la barrière (172.2O.3.73 ou 172.20.3.77 suivant le cas).
  3. Placez ensuite le logiciel service directement sur la racine du système linux de la barrière au moyen de la commande put.
  4. Vous pouvez maintenant vous déconnecter du service FTP afin de libérer les ressources.


Votre logiciels est ainsi téléchargé. Vous pouvez maintenant le lancer au travers du service telnet tel que cela vous est montré dans la rubrique suivante.
.

Activation ou désactivation du service

Lorsque vous alimentez votre barrière, il sera nécessaire de lancer le service de gestion. Il faut d'abord vérifier sa présence et au cas où, il faudra le télécharger comme nous venons de le faire dans la rubrique précédente. Si votre logiciel est bien présent, il faut alors l'activer en lançant la machine virtuelle java au moyen de la commande java comme cela vous est montré ci-dessous :

A tout moment, il est possible de désactiver un service. Pour cela, il faut connaître son numéro d'exécution au moyen de la commande ps. Ensuite, une fois que vous connaissez ce numéro, il faut le placer en argument de la commande kill.

 

Choix du chapitre Protocole de communication

Nous pouvons communiquer avec la barrière à l'aide de trois composants spécifiques, qui chacun comporte un certain nombre de méthodes adaptées :

  1. Affichage d'informations adaptées pour avertir le conducteur au moyen du composant Afficheur.
  2. Saisie du code secret donnant l'autorisation au conducteur de rentrer au moyen du composant Clavier.
  3. Pilotage de la barrière d'accès et contrôle des différents capteurs (état de la barrière et position du véhicule) au moyen du composant Barriere.


Actions spécifiques pour chaque composant

Voici ci-dessous l'ensembles des méthodes que le service est capable de gérer pour traiter le programme souhaité :

pour la classe Afficheur
affiche(x, y, message)
Affiche une chaîne de caractère à la position désirée suivant le couple <x, y> avec y=0 pour la première ligne et y=1 pour la deuxième ligne. De même, pour atteindre la position la plus à gauche, vous devez préciser la valeur 0 pour x.
afficheCaractere(caractère)
Affiche un seul caractère à la position du curseur actuel.
afficheMessages(premier, deuxième)
Affiche deux chaînes de caractères sur chacune des lignes.
clear()
Efface le ou les messages présents sur les afficheurs.
home()
Place le curseur à la première ligne et à la première colonne.
positionCursor(x, y)
Déplace la position du curseur suivant le couple <x, y> avec y=0 pour la première ligne et y=1 pour la deuxième ligne. L'affichage du prochain caractère (ou de la chaîne) se fera à partir de cet endroit là.
setCursor(vrai ou faux)
Demande ou pas l'affichage d'un curseur clignotant dans le cas où nous devons gérer la saisie du code d'entrée. Si vous désirez rendre actif votre curseur, voici ce qu'il faut faire : setCursor(true).
pour la classe Barriere
setCommandeSensMontee()
Cette méthode permet d'activer la montée de la barrière. Le système est protégé. Si la barrière est déjà en haut, aucune action n'est réalisée. Lorsque la barrière se trouve finalement en position haute, la valeur haut est renvoyée.
setCommandeSensDescente()
Cette méthode permet d'activer la descente de la barrière. Le système est protégé. Si la barrière est déjà en bas, aucune action n'est réalisée. Lorsque la barrière se trouve finalement en position basse, la valeur bas est renvoyée.
isInFDCH()
Indique l'état du capteur "Fin De Course Haut" permettant de savoir si la barrière est en position haute. Si c'est le cas, la valeur haut est renvoyée, sinon c'est la valeur non.
isInFDCL()
Indique l'état du capteur "Fin De Course Bas (Low)" permettant de savoir si la barrière est en position basse. Si c'est le cas, la valeur bas est renvoyée, sinon c'est la valeur non.
isBoucleAmont()
Indique l'état du capteur de présence de la voiture en entrée. Ce capteur est en réalité une boucle de courant placée en amont de la barrière. Si c'est le cas, la valeur entree est renvoyée, sinon c'est la valeur non.
isBoucleAval()
Indique l'état du capteur de présence de la voiture en sortie. Ce capteur est en réalité une boucle de courant placée en aval de la barrière. Si c'est le cas, la valeur sortie est renvoyée, sinon c'est la valeur non.
pour la classe Clavier
readClavier()
Permet de scanner le clavier physique et de récupérer le code de la touche enfoncée. La valeur du code renvoyé est :

-1 : si aucune touche n'a été enfoncée.
Un chiffre de 0 à 9 : si nous appuyons sur la touche correspondante.
16 : si nous appuyons sur la touche *.
10 : si nous appuyons sur la touche #.
1 : Appel gardien.
toucheEnfoncee()
Cette méthode fait appel à la méthode readClavier(). Elle est blocante. Elle attend qu'une touche soit effectivement enfoncée. La valeur du code renvoyée :

Un chiffre de 0 à 9 : si nous appuyons sur la touche correspondante.
16 : si nous appuyons sur la touche *.
10 : si nous appuyons sur la touche #.
1 : Appel gardien.

Définition du protocole utilisé pour communiquer avec le logiciel service installé dans la barrière

Maintenant que nous connaissons bien les différents intervenants, nous pouvons définir le protocole à respecter pour le bon déroulement de l'échange d'informations entre le client et le serveur. Le protocole est relativement simple puisqu'il s'agit d'échanger des chaînes de caractères avec toutefois un format bien spécifique. Ainsi, vous devez stipuler, dans votre chaîne de caractères :

  1. d'abord le nom du composant sur lequel vous devez lancer une action particulière ;
  2. préciser ensuite l'action qui vous intéresse ;
  3. éventuellement, donner le ou les arguments nécessaires à l'action souhaitée.

Attention, chacun de ses éléments devront être séparé par le caractère ":". Par ailleurs, le caractère espace " " dans votre chaîne de caractères est interprété comme tout autre caractère.

A titre d'exemple, voici la chaîne de caractères à envoyer pour avoir l'affichage suivant, tout en respectant le protocole que nous venons de définir :

Afficheur:afficheMessages:-   Bonjour!   L:-  **********  - . L'information de retour : OK.

Voici ci-dessous d'autres exemples de commande :

  1. Pour afficher un message à un endroit spécifique : Afficheur:affiche:1:0:Votre code - retour OK.
  2. Pour savoir si une voiture est présente en entrée du parking : Barriere:isBoucleAmont - retour entree (ou non).
  3. Pour lever la barrière : Barriere:setCommandeSensMontee - retour haut.
  4. Pour connaître la valeur saisie au clavier : Clavier:readClavier - retour -1.

 

Choix du chapitre Comment développer une application cliente

Après toute cette approche, qui nous a bien servie à comprendre le fonctionnement général de la barrière, nous allons maintenant commencer nos TPs. Le but est, je le rappelle, de créer des logiciels clients en C++ et sous Linux qui pilote la barrière à distance. L'application cliente doit donc entrer en communication avec le service présent dans le système embarqué de la barrière et résoudre l'ensemble des commandes souhaitées en respectant le protocole préconisé.

Les conditions et les règles à respecter pour créer un programme client

Nous allons mettre en oeuvre ensemble un tout petit programme qui affiche le message "Bonjour" sur la deuxième ligne de l'afficheur en sachant que dès qu'un client se connecte au service, la première ligne contient déjà le message "En service". Voici les règles à respecter pour élaborer ce petit programme.

  1. Dans un premier temps, il faudra systématiquement inclure le fichier <Socket.h> dans chacun de vos programes.

    Le fichier Socket.h doit être placé dans le répertoire /usr/include/c++/4.2/
    §

  2. Il faut ensuite créer un objet (une variable) de la classe Socket (type) avec le nom de votre choix.
  3. Au moment de cette déclaration, il faut préciser la localisation IP de la barrière sur laquelle vous désirez travailler : 172.20.3.73 pour la barrière réelle et 172.20.3.77 pour la maquette.
  4. Toujours au moment de cette déclaration, vous précisez le numéro de service avec lequel vous devez communiquer. Le logiciel de service de gestion de la barrière possède le numéro de service 5588. (Le service FTP possède le numéro 21, Le service Web possède le numéro 80, etc.).
  5. Une fois que cet objet est créé, et lorsque vous désirez lancer une action particulière à la barrière, vous l'écrivez en respectant le protocole requis dans une chaîne de caractères qui sert d'argument à la méthode envoyer().
  6. A chaque fois que vous utilisez la méthode envoyer(), il faut également récupérer le résultat de la commande au moyen de la méthode recevoir() de ce même objet.
  7. Enfin, lorsque vous avez fini d'envoyer toutes vos commandes, il faut en envoyer une dernière qui comporte le texte "fin". Même pour cette dernière commande, vous devez faire appel à la méthode recevoir().
  8. Voici ci-dessous le codage représentant toute la partie cliente qui correspond au programme désiré et qui tient compte de tous ces critères :

    #include <Socket.h>
    #include <iostream.h>
    
    int main() 
    {
      Socket barriere("172.20.3.77", 5588);
      
      barriere.envoyer("Afficheur:affiche:0:1:Bonjour");
      cout << barriere.recevoir() << endl;
      barriere.envoyer("fin");
      cout << barriere.recevoir() << endl;
      
      return 0;
    }
        
    Remarquez bien que nous avons besoin de très peu de lignes de code pour communiquer avec notre barrière et pour la commander suivant notre souhait.

Visualisation de la prise en compte de la commande

Le logiciel service de gestion de la barrière affiche tout un ensemble d'informations en temps réel. Pour les visualiser, employez le service TELNET. Vous verrez ainsi comment le service analyse la commande. Remarquez au passage que le service connaît l'adresse IP du client qui le sollicite :

Voici, pendant ce temps là, ce qu'il se passe côté client :

Les ressources nécessaires au bon fonctionnement du système global

Le service côté barrière : service.
Le fichier en-tête : Socket.h.
La version PDF du service : service.pdf.
La version PDF du fichier en-tête : SocketH.pdf.
La version HTML du service : service.html.
La version HTML du fichier en-tête : SocketH.html.

 

Choix du chapitre Applications clientes sans fonctions et sans objets

Vous êtes maintenant prêts pour élaborer vos propres applications clientes. Dans un premier temps, vous allez mettre en oeuvre une série de TPs sans passer par la fabrication de fonctions particulières ou sans passer par l'approche objet.

Dans tous vos TPs, utilisez systématiquement le service TELNET de la barrière afin de bien contrôler l'analyse qui est faite par le service, et par là, de contrôler le bon fonctionnement de votre application cliente. Les problèmes que vous pouvez rencontrer sont souvent dûs à une mauvaise écriture du protocole. Si c'est la cas, l'analyse ne pourra pas se faire correctement.

Il peut arriver que le service soit totalement planté. Dans ce cas là, il suffit de l'arrêter au moyen de la commande kill du système d'exploitation embarqué. Ensuite, vous pouvez le relancer au moyen de la commande : java service &.

Afficher sur la barrière, un message saisie avec le clavier de l'ordinateur client

Pour votre premier TP, vous allez faire de nouveau une gestion de l'afficheur de la barrière. La première ligne de l'afficheur doit proposer le texte "Bienvenue". La deuxième ligne de l'afficheur correspondra au message que l'utilisateur aura saisi sur son clavier d'ordinateur. Attention, le nombre de caractères que l'afficheur est capable de gérer est très limité. Il faut donc tenir compte de sa capacité.

Le programme client devra constamment être actif jusqu'à ce que l'utilisateur tape le mot "fin".

Pour ce TP, il est possible que vous ayez besoin de fonctions de traitement de chaîne, comme la copie, le calcul de la longueur d'une chaîne, etc. Pour cela, il suffit d'include le fichier <string.h>. Je rappelle ci-dessous les principales fonctions qui peuvent vous être utiles :

  1. Longueur d'une chaîne : strlen(chaine). Le retour de la fonction donne une valeur entière qui correspond à la longueur.
  2. Copie d'une chaine vers une autre : strcpy(destination, source).
  3. Concaténation (fusion) de chaînes : strcat(chaine, rajout). Après l'appel de cette fonction la variable chaine comporte en plus la chaîne rajout.
  4. Comparaison de deux chaînes : strcmp(chaine1, chaine2). Le retour de la fonction donne une valeur entière correspondant à la comparaison suivant l'ordre alphabétique avec -1 si la première chaîne se situe avant, 0 si les deux chaînes sont identiques et +1 si la première chaîne se situe après.
Corrections : Visualisation du résultat en html . Version en pdf .
.

Même TP, mais les deux messages doivent être automatiquement centrés

Comme le titre l'indique, j'aimerais cette fois-ci, que le message de bienvenue et votre propre message saisie au claver, soient systématiquement centrés sur l'afficheur. Par contre la fonctionnalité globale demeure inchangée.

Nous devons cette fois-ci réaliser un calcul numérique qui détermine la position du message. Cette valeur numérique peut éventuellement faire partie du protocole à envoyer. Du coup, dans ce cas là, il est nécessaire de transformer une valeur numérique en une chaîne de caractères.

Il existe une fonction sprintf() qui permet d'intégrer une valeur numérique à un emplacement précis de votre chaîne de caractères. Dans ce cas là, il faut spécifier un caractère de formatage qui indentifie le type de format à réaliser. Ce caractère de formatage doit être précédé du symbole % pour qu'il soit interprété comme un caractère de formatage. Il existe plusieurs types de format, mais les plus utilisés sont les suivants :
  1. %d : place une valeur entière (décimale) à cet endroit spécifique.
  2. %f : place une valeur réelle (flottante) à cet endroit précis.
  3. %s : place une chaîne de caractères à cet endroit précis.
  4. %c : place un caractère à cet endroit précis.
  5. etc.

Voici la signature de la fonction : sprintf(résultat, format, variable1, variable2, ...)

char chaine[35];
int i = 22;
double x = 3.5;
char h = 'A';
char z[4] = "fin";
sprintf(chaine, "entier = %d, réel = %f, car = %c, c'est la %s", i, x, h, z);

Après cette suite d'instructions, chaine prend la valeur suivante :

chaine <= "entier = 22, réel = 3.5, car = A, c'est la fin"

Corrections : Visualisation du résultat en html. Version en pdf .
.

Identifier la présence d'un véhicule en entrée ou en sortie et afficher l'état de ces capteurs chacun sur une ligne de l'afficheur

Avec ce TP, nous allons maintenant contrôler l'état des boucles de courant et ainsi savoir si un véhicule se présente en entrée ou en sortie. Votre programme va donc consister à évaluer ces présences, et au cas où, vous vous servez de l'afficheur de la barrière pour visualiser chacun de ces états.

  1. Si un véhicule est présent en entrée, la première ligne de l'afficheur de la barrière doit proposer le message "Entrer -->".
  2. Si un véhicule est présent en sortie, la deuxième ligne de l'afficheur de la barrière doit proposer le message "<-- Sortir ".

Le programme doit fonctionner indéfiniment. Attention, l'affichage doit être parfaitement lisible sans clignotement intempestif et sans caractères supplémentaires aléatoires.

Corrections : Visualisation du résultat en html. Version pdf .
.

Identifier la position de la barrière et la piloter en conséquence

Vous allez maintenant piloter la barrière. Si cette dernière se trouve en bas, votre programme doit tout simplement lever la barrière en position haute et s'arrêter là. Si vous relancez votre programme, cette fois-ci la barrière doit redescendre jusqu'en bas.

Votre programme doit donc au préalable contrôler la position de la barrière afin d'agir correctement sur celle-ci. Prévoyer un affichage sur votre application cliente qui donne tous les états de fonctionnement de la barrière, comme cela vous est montré ci-dessous.


Effectuez le test sur la véritable barrière, donc à l'adresse IP : 172.20.3.73. Vérifiez alors que la partie puissance est bien opérationnelle. Il est effectivement possible que le bouton d'arrêt d'urgence soit enfoncé. Il faut alors le désactiver avec la clé prévue à cet effet.

Corrections : Visualisation du résultat en html . Version pdf .
.

Lever automatiquement la barrière lorsqu'un véhicule se présente en sortie.

Le but de ce TP est de permettre la sortie des véhicules qui se trouvent encore dans le parking. Le démarrage de votre application cliente doit, dans un premier temps, abaisser la barrière dans le cas où cette dernière n'est pas placée en positions initiale. Une fois que le système est opérationnel avec toutes les bonnes conditions de démarrage, vous pouvez alors élaborer un système automatique qui ouvre la barrière dès qu'un véhicule se présente en sortie. La barrière doit redescendre également automatiquement lorsque la voiture sort complètement du parking.

Attention vous devez suivre impérativement le scénario suivant pour maîtriser parfaitement la bonne gestion des boucles de courant lorsqu'un véhicule se présente en sortie, savoir :
- il actionne d'abord la boucle aval.
- le véhicule passe ensuite au-dessus des deux boucles.
- le véhicule quitte ensuite la boucle aval, seule la boucle amont est encore active.
- enfin, le véhicule sort complètement du parking, c'est-à-dire que la boucle amont est elle-même désactivée.

Corrections : Visualisation du résultat en html . Version pdf .
.

Contrôler la saisie du clavier de la borne d'accès afin de permettre une authentification

L'application cliente que vous allez développer, s'occupe uniquement de contrôler un code secret saisie au clavier. Le code est composé de 4 chiffres. Lorsque l'application cliente démarre, l'afficheur de la borne d'accès doit proposer les messages suivants :

  1. "Bonjour" sur la première ligne de l'afficheur.
  2. "Code ? " sur la deuxième ligne de l'afficheur.

Lorsque nous tapons notre code sur le clavier de la borne d'accès, chaque caractère saisie doit être suivi de l'affichage du caractère '#' sur l'afficheur afin de bien indiquer la prise en compte du chiffre choisi.

Si le code introduit n'est pas bon, à cause d'une mauvaise saisie, nous devons préciser le message "Code incorrect" sur la première ligne de l'afficheur. Nous sommes obligés alors de ressaisir le code en entier.

Dans le cas contraire, votre code est correct, le programme s'arrête alors, avec toutefois l'affichage :

  1. "Code bon" sur la première ligne de l'afficheur.
  2. "Entrez..." sur la deuxième ligne de l'afficheur.
Corrections : Visualisation du résultat en html . Version pdf .
.

Récupérer et valider la saisie du code secret introduit au niveau de la barrière

Le but de ce TP est de contrôler l'authentification de la personne habilitée à penétrer dans la parking privé. Pour cela, dès qu'un véhicule se présente à l'entrée du parking, le message "Bonjour" sur la première ligne, et le message "Code ? " sur la deuxième ligne, apparaissent sur l'afficheur de la barrière.

  1. L'automobiliste doit alors saisir le mot de passe de 4 chiffres.
  2. Après chaque chiffre introduit, le symbole # doit s'afficher à la suite du message "Code : ", afin de bien montrer que la saisie du chiffre a bien été pris en compte.
  3. Si le code introduit n'est pas bon, à cause d'une mauvaise saisie, l'automobiliste doit alors en être averti à l'aide du message "Code incorrect" sur la première ligne de l'afficheur. L'automobiliste doit alors ressaisir son code en entier.
  4. Si le code est correct, la barrière s'ouvre et laisse passer la voiture.
  5. Lorsque la voiture est définitivement entrée dans le parking, c'est-à-dire, lorsque la boucle aval est désactivée (après être activée), la barrière doit alors redescendre automatiquement.
Corrections : Visualisation du résultat en html . Version pdf .
.

 

Choix du chapitre Applications clientes à l'aide de fonctions spécifiques

Nous allons reprendre quelques-uns des TPs que nous venons de mettre en oeuvre afin que le code source soit plus facile à écrire. Effectivement, vous remarquez qu'il est souvent nécessaire de faire plusieurs traitements de chaîne de caractères pour une seule action. L'écriture du protocole pour des actions très souvent utilisées devient donc vite lassant et source d'erreur.

Il est préférable de prévoir un certain nombre de fonctions qui encapsulent toute la gestion du protocole, avec tous les traitements de chaînes de caractères adaptés, en relation avec les trois composants de la barrière, savoir : Afficheur, Clavier et Barrière.

Du coup, lorsque nous utilisons ces fonctions, nous n'avons plus à nous soucier du protocole à mettre en oeuvre, et nous n'avons plus besoin de faire du traitement de chaînes de caractères spécifiques. Ainsi, notre raisonnement et notre action se situe à plus haut niveau et donc le développement devient plus simple à réaliser.

Nous allons en profiter pour mettre au point, de façon plus intuitive, des fonctions spécifiques qui correspondent directement à l'action finale souhaitée. Ainsi, par exemple, lorsque nous proposons un affichage sur les deux lignes de l'Afficheur de la borne d'accès, il serait préférable d'effacer systèmatiquement au préalable les anciens messages afin d'éviter les caractères intempestifs.

Mise en oeuvre de fonctions spécifiques à la gestion de l'afficheur

Vous allez développer l'ensemble des fonctions prévues pour l'affichage. La définition de chacune de ces fonctions est relativement réduite. Ainsi, nous pouvons les déclarer inline. L'idéal est de les placer dans un seul fichier en-tête <afficheur.h>.

Dans ce cadre là, respectez bien l'ossature normale d'un fichier en-tête en prenant en compte les directives de compilation usuelles.
.

Ces fonctions doivent pouvoir fonctionner aussi bien sur la vrai barrière que sur le modèle réduit. Chaque fonction doit donc proposer, en premier argument, la barrière que vous souhaitez piloter. Le premier paramètre est systématiquement de type Socket.

Nous allons revoir chacune de ces fonctions, avec la signature qui tient compte des critères que nous venons d'évoquer. A cela, nous rajoutons deux fonctions supplémentaires qui utilisent les compétences de ces dernières et qui proposent :

  1. Pour la première, un affichage sur deux lignes avec un effacement au préalable.
  2. La deuxième fonction propose la même chose mais en centrant automatiquement les deux messages envoyés.

Voici ci-dessous l'ensemble des fonctions à implémenter, avec leur signature respective :

inline void clear(Socket &barriere)
Efface le ou les messages présents sur les afficheurs.
inline void home(Socket &barriere)
Place le curseur à la première ligne et à la première colonne.
inline void setCursor(Socket &barriere, bool visible)
Demande ou pas l'affichage d'un curseur clignotant dans le cas où nous devons gérer la saisie du code d'entrée.
inline void positionCursor(Socket &barriere, int x, int y)
Déplace la position du curseur suivant le couple <x, y> avec y=0 pour la première ligne et y=1 pour la deuxième ligne. L'affichage du prochain caractère (ou de la chaîne) se fera à partir de cet endroit là.
inline void affiche(Socket &barriere, int x, int y, char *message)
Affiche une chaîne de caractère à la position désirée suivant le couple <x, y> avec y=0 pour la première ligne et y=1 pour la deuxième ligne. De même, pour atteindre la position la plus à gauche, vous devez préciser la valeur 0 pour x.
inline void afficheCaractere(Socket &barriere, char caractere)
Affiche un seul caractère à la position du curseur actuel.
inline void afficheMessages(Socket &barriere, char *premier, char *deuxieme)
Affiche deux chaînes de caractères sur chacune des lignes.
inline void affiche(Socket &barriere, char *premier, char *deuxieme)
Affiche deux chaînes de caractères sur chacune des lignes avec un effacement préliminaire.
inline void centre(Socket &barriere, char *premier, char *deuxieme)
Affiche deux chaînes de caractères sur chacune des lignes avec un effacement préliminaire. Chacune de ces chaîne est automatiquement centrée sur la ligne concernée.
Corrections : Visualisation du résultat en html . Version pdf .
.

Utilisation des fonctions d'affichage : Afficher sur la barrière, un message saisie avec le clavier de l'ordinateur client

A titre d'exemple, reprenons le premier TP de gestion de l'afficheur de la barrière. Je rappelle l'intitulé :

La première ligne de l'afficheur doit proposer le texte "Bienvenue". La deuxième ligne de l'afficheur correspondra au message que l'utilisateur aura saisi sur son clavier d'ordinateur. Attention, le nombre de caractères que l'afficheur est capable de gérer est très limité. Il faut donc tenir compte de sa capacité. Le programme client devra constamment être actif jusqu'à ce que l'utilisateur tape le mot "fin".

Cette foisi-ci toutefois, la valeur de retour du protocole n'est plus du tout affiché, puisque les fonctions implémentées ne le permettent pas.
.

Corrections : Visualisation du résultat en html . Version pdf .
.

Utilisation des fonctions d'affichage : Même TP, mais les deux messages doivent être automatiquement centrés

Comme le titre l'indique, j'aimerais cette fois-ci, que le message de bienvenue et votre propre message saisie au claver, soient systématiquement centrés sur l'afficheur. Par contre la fonctionnalité globale demeure inchangée.

Corrections : Visualisation du résultat en html . Version pdf .
.

Mise en oeuvre de fonctions spécifiques à la gestion globale de la barrière

Nous allons appliquer la même démarche que pour la gestion de l'affichage. Vous allez développer l'ensemble des fonctions prévues pour la gestion globale de la barrière. Encore une fois, la définition de chacune de ces fonctions est relativement réduite. Ainsi, nous pouvons les déclarer inline. L'idéal est de les placer dans un seul fichier en-tête, qui cette fois-ci, s'appelle <barriere.h>.

Je rappelle que ces fonctions doivent pouvoir fonctionner aussi bien sur la vrai barrière que sur le modèle réduit. Chaque fonction doit donc proposer, en argument, la barrière que vous souhaitez piloter. Le paramètre unique est finalement systématiquement de type Socket.

Voici ci-dessous l'ensemble des fonctions à implémenter, avec leur signature respective :

inline void setCommandeSensMontee(Socket &barriere)
Cette méthode permet d'activer la montée de la barrière. Si la barrière est déjà en haut, aucune action n'est réalisée.
inline void setCommandeSensDescente(Socket &barriere)
Cette méthode permet d'activer la descente de la barrière. Si la barrière est déjà en bas, aucune action n'est réalisée.
inline bool isInFDCH(Socket &barriere)
Indique l'état du capteur "Fin De Course Haut" permettant de savoir si la barrière est en position haute.
inline bool isInFDCL(Socket &barriere)
Indique l'état du capteur "Fin De Course Bas (Low)" permettant de savoir si la barrière est en position basse.
inline bool isBoucleAmont(Socket &barriere)
Indique l'état du capteur de présence de la voiture en entrée.
inline bool isBoucleAval(Socket &barriere)
Indique l'état du capteur de présence de la voiture en sortie.
Corrections : Visualisation du résultat en html . Version pdf .
.

Utilisation des fonctions de la barrière : Identifier la présence d'un véhicule en entrée ou en sortie

A titre d'exemple, reprenons le premier TP qui permet de contrôler l'état des boucles de courant et ainsi savoir si un véhicule se présente en entrée ou en sortie. Votre programme va donc consister à évaluer ces présences, et au cas où, vous vous servez de l'afficheur de la barrière pour visualiser chacun de ces états.

  1. Si un véhicule est présent en entrée, la première ligne de l'afficheur de la barrière doit proposer le message "Entrer -->".
  2. Si un véhicule est présent en sortie, la deuxième ligne de l'afficheur de la barrière doit proposer le message "<-- Sortir ".

Le programme doit fonctionner indéfiniment. Attention, l'affichage doit être parfaitement lisible sans clignotement intempestif et sans caractères supplémentaires aléatoires.

Corrections : Visualisation du résultat en html. Version pdf .
.

Utilisation des fonctions de la barrière : Identifier la position de la barrière et la piloter en conséquence

Vous allez maintenant piloter la barrière. Si cette dernière se trouve en bas, votre programme doit tout simplement lever la barrière en position haute et s'arrêter là. Si vous relancez votre programme, cette fois-ci la barrière doit redescendre jusqu'en bas.

Votre programme doit donc au préalable contrôler la position de la barrière afin d'agir correctement sur celle-ci. Prévoyer un affichage sur votre application cliente qui donne tous les états de fonctionnement de la barrière, comme cela vous est montré ci-dessous.


Effectuez le test sur la véritable barrière, donc à l'adresse IP : 172.20.3.73. Vérifiez alors que la partie puissance est bien opérationnelle. Il est effectivement possible que le bouton d'arrêt d'urgence soit enfoncé. Il faut alors le désactiver avec la clé prévue à cet effet.

Corrections : Visualisation du résultat en html . Version pdf .
.

Utilisation des fonctions de la barrière : Lever automatiquement la barrière lorsqu'un véhicule se présente en sortie.

Rappelons que le but de ce TP est de permettre la sortie des véhicules qui se trouvent encore dans le parking. Le démarrage de votre application cliente doit, dans un premier temps, abaisser la barrière dans le cas où cette dernière n'est pas placée en positions initiale. Une fois que le système est opérationnel avec toutes les bonnes conditions de démarrage, vous pouvez alors élaborer un système automatique qui ouvre la barrière dès qu'un véhicule se présente en sortie. La barrière doit redescendre également automatiquement lorsque la voiture sort complètement du parking.

Attention vous devez suivre impérativement le scénario suivant pour maîtriser parfaitement la bonne gestion des boucles de courant lorsqu'un véhicule se présente en sortie, savoir :
- il actionne d'abord la boucle aval.
- le véhicule passe ensuite au-dessus des deux boucles.
- le véhicule quitte ensuite la boucle aval, seule la boucle amont est encore active.
- enfin, le véhicule sort complètement du parking, c'est-à-dire que la boucle amont est elle-même désactivée.

Corrections : Visualisation du résultat en html . Version pdf .
.

Mise en oeuvre de fonctions spécifiques à la gestion du clavier de la borne d'accès

Vous allez développer l'ensemble des fonctions prévues pour la gestion du clavier de la borne d'accès. Encore une fois, la définition de chacune de ces fonctions est relativement réduite. Ainsi, nous pouvons les déclarer inline. L'idéal est de les placer dans un seul fichier en-tête, qui cette fois-ci, s'appelle <clavier.h>.

Je rappelle que ces fonctions doivent pouvoir fonctionner aussi bien sur la vrai barrière que sur le modèle réduit. Chaque fonction doit donc proposer, en argument, la barrière que vous souhaitez piloter. Le paramètre unique est finalement systématiquement de type Socket.

Voici ci-dessous l'ensemble des fonctions à implémenter, avec leur signature respective, en sachant que je rajoute une fonction complète de gestion de saisie du code secret :

inline int readClavier(Socket &barriere)
Permet de scanner le clavier physique et de récupérer le code de la touche enfoncée. La valeur du code renvoyé est :

-1 : si aucune touche n'a été enfoncée.
Un chiffre de 0 à 9 : si nous appuyons sur la touche correspondante.
16 : si nous appuyons sur la touche *.
10 : si nous appuyons sur la touche #.
1 : Appel gardien.
inline int toucheEnfoncee(Socket &barriere)
Cette méthode fait appel à la méthode readClavier(). Elle est blocante. Elle attend qu'une touche soit effectivement enfoncée. La valeur du code renvoyée :

Un chiffre de 0 à 9 : si nous appuyons sur la touche correspondante.
16 : si nous appuyons sur la touche *.
10 : si nous appuyons sur la touche #.
1 : Appel gardien.
inline bool codeBon(Socket &barriere, int codeSecret)
Contrôler la saisie du code de l'automobiliste (4 chiffres). Il doit correspondre au code secret passé en argument de la fonction.
Corrections : Visualisation du résultat en html . Version pdf .
.

Utilisation des fonctions du clavier : Contrôler la saisie du clavier de la borne d'accès afin de permettre une authentification

Reprenons l'application cliente précédente. Je rappelle qu'elle s'occupe uniquement de contrôler un code secret saisie au clavier. Le code est composé de 4 chiffres. Lorsque l'application cliente démarre, l'afficheur de la borne d'accès doit proposer les messages suivants :

  1. "Bonjour" sur la première ligne de l'afficheur.
  2. "Code ? " sur la deuxième ligne de l'afficheur.

Lorsque nous tapons notre code sur le clavier de la borne d'accès, chaque caractère saisie doit être suivi de l'affichage du caractère '#' sur l'afficheur afin de bien indiquer la prise en compte du chiffre choisi.

Si le code introduit n'est pas bon, à cause d'une mauvaise saisie, nous devons préciser le message "Code incorrect" sur la première ligne de l'afficheur. Nous sommes obligés alors de ressaisir le code en entier.

Dans le cas contraire, votre code est correct, le programme s'arrête alors, avec toutefois l'affichage :

  1. "Code bon" sur la première ligne de l'afficheur.
  2. "Entrez..." sur la deuxième ligne de l'afficheur.
Corrections : Visualisation du résultat en html . Version pdf .
.

Utilisation des fonctions du clavier : Récupérer et valider la saisie du code secret introduit au niveau de la barrière

Le but de ce TP est de contrôler l'authentification de la personne habilitée à penétrer dans la parking privé. Pour cela, dès qu'un véhicule se présente à l'entrée du parking, le message "Bonjour" sur la première ligne, et le message "Code ? " sur la deuxième ligne, apparaissent sur l'afficheur de la barrière.

  1. L'automobiliste doit alors saisir le mot de passe de 4 chiffres.
  2. Après chaque chiffre introduit, le symbole # doit s'afficher à la suite du message "Code : ", afin de bien montrer que la saisie du chiffre a bien été pris en compte.
  3. Si le code introduit n'est pas bon, à cause d'une mauvaise saisie, l'automobiliste doit alors en être averti à l'aide du message "Code incorrect" sur la première ligne de l'afficheur. L'automobiliste doit alors ressaisir son code en entier.
  4. Si le code est correct, la barrière s'ouvre et laisse passer la voiture.
  5. Lorsque la voiture est définitivement entrée dans le parking, c'est-à-dire, lorsque la boucle aval est désactivée (après être activée), la barrière doit alors redescendre automatiquement.
Corrections : Visualisation du résultat en html . Version pdf .
.

 

Choix du chapitre Applications clientes à l'aide d'objets spécifiques

Avec une approche structurée, comme l'élaboration de fonctions spécifiques, apporte un confort de programmation beaucoup plus intéressant qu'une programmation dont l'écriture propose des actions "à la volée" sans aucune structure particulière. Effectivement, une fois que les fonctions sont élaborées, il est très facile de proposer des actions spécifiques de haut niveau sur la barrière, sans se préoccuper du protocole sous-jacent, avec déjà une certaine complexité sur le fonctionnement global sans qu'il y ait beaucoup de lignes de codes à écrire.

Nous remarquons, toutefois, un petit inconvénient qui à la longue devient vite génant. Effectivement, à chaque appel de fonction, quelle que soit sa nature, nous sommes obligés de proposer en premier argument, l'objet relatif au point de communication avec le service lancé du côté de la barrière. Je rappelle que cet objet nous permet de préciser sur quelle barrière (vraie barrière ou modèle réduit) nous devons lancer les actions désirées. C'est dommage de devoir faire appel systématiquement à cet argument là. Il serait plutôt souhaitable que cette information soit mémorisée (encapsulée) une fois pour toute, dans une structure qui peut prendre en compte cette encapsulation (cette mémorisation).

La classe répond tout-à-fait à ce critère puique, en plus du comportement spécifique lié à l'ensemble des actions souhaités au travers de méthode adaptées, il est possible de mettre en oeuvre un ou plusieurs attributs qui sont là pour enregistrer des données paticulières à l'état actuel du ou des objets, comme par exemple, notre point de communication (Socket). Le gros avantage ici, c'est que la signature des méthodes va se trouver considérablement allégée par rapport aux fonctions équivalentes que nous avons déjà mis au point, puisqu'il ne sera plus nécessaire de demander ce paramètre de connexion. L'objet sera déjà au courant sur quelle barrière nous devons dialoguer.

Avec cette nouvelle démarche, correspondant à la phylosophie objet, nous pouvons envisager d'élaborer trois classes qui représenterons les éléments physiques de la barrière :

  1. La classe Afficheur.
  2. La classe Barriere.
  3. La classe Clavier.

Je rappelle que les classes sont des "moules" définissant le comportement général de chaque objet. Il faut donc créer les objets correpondant à chacune de ces classes si nous désirons travailler avec l'ensemble des éléments de la barrière. Un même "moule" peut servir à la création de plusieurs pièces. Ainsi, par exemple, dans un même programme, nous pouvons envisager de construire deux objets relatifs à la même classe Afficheur, le premier représentant l'afficheur de la maquette, le deuxième représentant l'afficheur de la vraie barriere.

L'avantage de cette phylosophie objet c'est qu'il est très facile de conceptualiser les éléments sur lequel nous travaillons. Par exemple ici, chaque objet correspond finalement à la réalité d'un objet physique.

Elaboration des trois classes : Afficheur, Barriere et Clavier

A l'aide de trois fichiers séparés, vous allez mettre en place les trois classes Afficheur, Barriere et Clavier qui sont utiles à la bonne gestion complète de la borne d'accès. Vous n'avez pas tout à refaire, puique le contenu des fonctions que nous avons mis en place est tout-à-fait similaire (à quelques exceptions près). C'est la signature qui diffère, et surtout ces fonctions deviennent des méthodes des classes concernées.

Il faut prévoir toutefois, pour chaque classe, un constructeur qui permet de récupérer le point de communication avec la bonne barrière. Ainsi, chaque objet de la borne d'accès relatifs aux classes Afficheur, Barriere et Clavier possèderons chacun une référence vers l'objet unique (il s'agit bien également d'un objet) de communication.

Ce point de communication, comme nous l'avons évoqué plus haut, sera un attribut de chaque classe.
.

Le gros avantage de la programmation objet, c'est que le code est encore plus lisible que dans la programmation structurée (en étant toutefois plus verbeux). En effet, lorsque nous voulons effacer le contenu de l'afficheur, dans la programmation structurée, nous faisons juste appel à la fonction clear(), pourtant rien ne dit lorsque nous écrivons cette instruction, sur quel élément cette fonction va agir. Dans le cas de la programmation objet, nous spécifions d'abord l'objet sur lequel nous travaillons et nous lançons ensuite l'action désirée, par exemple afficheur.clear(), ce qui est de loin beaucoup plus compréhensible.

Corrections :

Pénétrer dans la parking privé avec saisie du code d'accès

Comme pour les fonctions, nous n'allons pas reprendre la totalité des TPs. Je vous propose de remprendre uniquement le dernier TP qui permet d'introduire le véhicule dans le parking privé à l'aide d'un code d'accès.

Cette fois-ci, vous nommerez votre point de communication service en lieu et place de barriere. Pensez-bien à créer chacun des objets représentant la borne d'accès : l'afficheur, le clavier et la barrière elle-même .

Corrections : Visualisation du résultat en html . Version pdf .
.

Proposer de nouvelles fonctionnalités à la classe Afficheur

Avec le concept objet, nous sommes en présence d'une programmation vraiment modulaire. Ainsi, il est très facile de faire des mises à jour en proposant de nouvelles fonctionnalités. Nous pourrions, par exemple, faire en sorte que la classe Afficheur représente au mieux l'afficheur réel de la barrière concernée. Ainsi, il serait intéressant de savoir à tout moment l'affichage qui est proposé sur chacune des lignes de l'afficheur.

Il s'agit, en fait, de mémoriser toutes les actions réalisées sur l'afficheur en gérant un historique des événements. Ainsi, il est possible, à tout moment, de savoir les messages qui sont réellements sur l'afficheur physique, sans toutefois faire un appel réseau, en consultant uniquement l'historique interne de la classe Afficheur.

Dans ce cadre là, la classe Afficheur est réellement un représentant de l'afficheur physique de la barrière concernée.
.


Par rapport au code source du client:
#include <Socket.h>
#include <barriere.h>
#include <clavier.h>

int main() 
{         
  Socket service("172.20.3.77", 5588);
  Afficheur afficheur(service);

  cout << "Ligne 1 : " << afficheur.getLigneUn() << endl;
  cout << "Ligne 2 : " << afficheur.getLigneDeux() << endl;  
  cout << "Attente de 5 s ..." << endl;
  sleep(5);
  
  afficheur.affiche(0, 0, "Bonjour");
  afficheur.afficheCaractere('!');
  afficheur.positionCursor(15, 1);
  afficheur.afficheCaractere('F');

  cout << "Ligne 1 : " << afficheur.getLigneUn() << endl;
  cout << "Ligne 2 : " << afficheur.getLigneDeux() << endl;
  cout << "Attente de 10 s ..." << endl;
  sleep(10);
  
  afficheur.affiche("Salut", "Bienvenue");
  for (int i=0; i<3; i++) 
    afficheur.afficheCaractere('.');
  
  cout << "Ligne 1 : " << afficheur.getLigneUn() << endl;
  cout << "Ligne 2 : " << afficheur.getLigneDeux() << endl;  
  
  service.envoyer("fin");
  service.recevoir();
  
  return 0;
}
Voici le résultat obtenu :

Vous remarquez la présence de deux nouvelles méthodes, getLigneUn() et getLigneDeux() à la classe Afficheur, qui renvoient le contenu de chaque lignes de l'afficheur. Dans le cas où aucun caractère ne s'affiche, c'est le symbole * qui est affiché en lieu et place, afin de bien illustrer la prise en compte de tous les caractères de chaque ligne.

Pour que ces méthodes puissent renvoyer ces valeurs, il est nécessaire d'avoir des attributs correspondants qui servent d'historique des événements recensés, ceux qui correspondent à l'affichage réel sur la barrière concernée. Attention, lorsque nous faisons appel à la méthode afficheCaractere(), il faut que le caractère s'affiche effectivement au bon endroit, ce qui implique de gérer en même temps la position du curseur.

A la suite de toutes ces considérations, à la classe Afficheur déjà construite, voici finalement tout ce que nous devons mettre en place :

  1. Rajouter un attribut ligneUn qui est une chaîne de 17 caractères.
  2. Rajouter un attribut ligneDeux qui est une chaîne de 17 caractères.
  3. Rajouter un attribut curseur qui est une structure de deux entiers x (représentant les colonnes de l'afficheur) et y (représentant la ligne de l'afficheur).
  4. Revoir toutes les méthodes de la classe afin que chaque action réalisée sur l'afficheur physique soit automatiquement répercutée sur ces deux nouveaux attributs ligneUn et ligneDeux.
  5. Revoir également toutes les méthodes de la classe afin que chaque action réalisée sur l'afficheur physique permette de connaître exactement, et à tout moment, la position réelle du curseur.
  6. Rajouter une méthode getLigneUn() qui renvoie le contenu complet de la ligne 1 de l'afficheur.
  7. Rajouter une méthode getLigneDeux() qui renvoie le contenu complet de la ligne 2 de l'afficheur.
  8. Prévoir une méthode supplémentaire privée effacerLignes() qui permet d'avoir deux lignes vierges (remplies du symbole *) sur nos historiques.
Corrections : Visualisation du résultat en html . Version pdf .

Grâce à la programmation objet, nous commençons à mettre en place des programmes clients très sophistiqués sans écrire beaucoup de lignes de code. En développement structuré, uniquement donc à l'aide de fonctions, il serait très difficile de prévoir une telle sophistication. Effectivement la gestion d'un historique, comme vous l'avez découvert, est relativement complexe. Imaginez de développer un historique pour plusieurs afficheurs, donc pour plusieurs barrières. Avec des fonctions uniquement, ce serait vite intordable. Dans le cas de la programmation objet, il suffit de définir le comportement global dans une seule classe. Ensuite chaque objet récupère ce comportement et surtout chacun possède son propre historique. Vous pouvez sans problème gérer dix barrières avec des affichages indépendants. L'utilisation des dix objets demeure toujours aussi facile. Toute la complexité est cachée à l'intérieur des méthodes (encapsulation) de la classe. Pour l'utilisateur des classes, il doit juste faire appel aux méthodes, et il obtient automatiquement la valeur désirée. Rien de plus simple.

Proposer de nouvelles fonctionnalités à la classe Clavier

Dans le même ordre d'idée, je vous propose de revoir la classe Clavier afin de proposer de nouvelles propriétés. Nous allons la rendre un peu plus sophistiquée en restant modeste toutefois. Je désire juste connaître, à tout instant, la dernière touche qui a été enfoncée, et savoir également, à tout instant, si la saisie du code secret était valide ou pas.

Finalement, à la classe Clavier déjà construite, vous devez :

  1. Rajouter un attribut derniereTouche qui est un entier et qui correspond à la dernière touche enfoncée.
  2. Rajouter un attribut valide qui est un booléen qui mémorise la validation du code secret.
  3. Revoir toutes les méthodes de la classe afin que chaque action réalisée sur le clavier physique soit automatiquement répercutée sur ces deux nouveaux attributs derniereTouche et valide.
  4. Rajouter une méthode getDerniereTouche() qui renvoie la dernière touche utilisée par l'automobiliste.
  5. Rajouter une méthode isValide() qui renvoie la validité du code secret.
Pour tester les nouvelles fonctionnalités, voici le code client à saisir
#include <Socket.h>
#include <barriere.h>
#include <clavier.h>

int main() 
{         
  Socket service("172.20.3.77", 5588);
  const int codeSecret = 7485;
  Afficheur afficheur(service);
  Clavier clavier(service);
   
  // solliciter la saisie du code
  afficheur.affiche("Bonjour", "Code ? ");

  // récupérer le bon code secret
  do {
    // si le code est incorrect, avertir l'automobiliste
    if (!clavier.codeBon(codeSecret)) afficheur.affiche("Code incorrect", "Code ? ");      
  }
  while (!clavier.isValide());

  // afficher un message précisant la bonne valeur du code
  afficheur.affiche("Code bon", "Entrez...");
     
  // clôturer la session cliente pour libérer les ressources du service
  service.envoyer("fin");
  service.recevoir(); 

  return 0;
}

Cette fois-ci, grâce à la méthode isValide(), il n'est plus nécessaire de prévoir une variable intermédiaire de type booléen pour concerver la valeur du test donnée par la méthode codeBon(). Encore une fois, la mise au point d'une classe de haut niveau permet d'utiliser des objets plus simplement de façon intuitive.

Corrections : Visualisation du résultat en html . Version pdf .

Proposer de nouvelles fonctionnalités à la classe Barriere

Je vous propose, pour finir avec la mise à jour des classes, de changer le comportement de la classe Barrière. Cette fois-ci, je ne mets pas en place un historique, mais je préfère contrôler de façon plus pointu à la fois la position de la barrière et d'autre part la localisation du véhicule par rapport à la borne d'accès. Pour cela, vous allez :

  1. Créer une énumération Position qui précise la position de la barriere. Trois valeurs sont possibles : enHaut, auMilieu, enBas.
  2. Créer une énumération Situation qui précise la localisation du véhicule par rapport à la borne d'accès. Quatre valeurs sont possibles : enEntree, enDessous, enSortie et aucune.
  3. Rajouter à la classe Barriere une méthode getPosition() qui renvoie la position réelle de la barriere suivant les trois critères précisés plus haut.
  4. Rajouter à la classe Barriere une méthode getSituation() qui renvoie la localisation réelle de la voiture suivant les quatres critères précisés plus haut.
Pour tester les nouvelles fonctionnalités, voici le code client à saisir
#include <Socket.h>
#include <barriere.h>
#include <clavier.h>

int main() 
{         
  Socket service("172.20.3.73", 5588);
  Afficheur afficheur(service);
  Barriere barriere(service);
  Clavier clavier(service);
  const int codeSecret = 7485;

  // descendre la barrière au départ si elle n'est pas en bas
  if (barriere.getPosition() != enBas) barriere.setCommandeSensDescente();
  
  while (true) 
  {   
    // Message d'invite
    afficheur.clear();
    afficheur.affiche(0, 0, "Bienvenue...");
 
    // contrôler la présence d'un véhicule en entrée
    while (barriere.getSituation() != enEntree);
    
     // solliciter la saisie du code
    afficheur.affiche("Bonjour", "Code ? ");

    // récupérer le bon code secret
    do {
      // si le code est incorrect, avertir l'automobiliste
      if (!clavier.codeBon(codeSecret)) afficheur.affiche("Code incorrect", "Code ? ");      
    }
    while (!clavier.isValide());

    // afficher un message précisant la bonne valeur du code
    afficheur.affiche("Code bon", "Entrez..."); 
    
    // lever la barrière pour permettre l'entrée du véhicule
    barriere.setCommandeSensMontee();
    
    // attendre que le véhicule pénètre complètement dans le parking
    while (barriere.getSituation() != enSortie); 
    while (barriere.getSituation() == enSortie);
    
    // descendre la barrière une fois que le véhicule est bien entré
    barriere.setCommandeSensDescente();   
  }
 
  return 0;
}
Corrections : Visualisation du résultat en html . Version pdf .

Les énumérations que nous venons de mettre en oeuvre sont déclarées à l'extérieur de la classe Barriere. Le code est plus simple, mais il est dans un sens moins lisible et surtout moins sécurisés. Effectivement, nous rajoutons des notions comme enBas, enDessous, etc. qui sont séparées de la barrière. Rien n'empêche de prendre ces énumérateurs pour d'autres applications qui n'ont rien à voir avec cette dernière.

Il serait souhaitable que l'ensemble de ces définitions, donc ces deux énumérations, soient placées à l'intérieur de la classe Barriere elle-même, en zone publique. Cela paraît normal puisque ces notions sont justement rattâchées à la barrière, et non pas à d'autres applications tierces.

Attention, nous devons, dès lors, rajouter systématiquement le préfixe Barriere, suivi de l'opérateur de portée ::, puisque la la définition des énumérations se trouvent à l'intérieur de celle-ci.

Modification du code client en conséquence :
#include <Socket.h>
#include <barriere.h>
#include <clavier.h>

int main() 
{         
  Socket service("172.20.3.73", 5588);
  Afficheur afficheur(service);
  Barriere barriere(service);
  Clavier clavier(service);
  const int codeSecret = 7485;

  // descendre la barrière au départ si elle n'est pas en bas
  if (barriere.getPosition() != Barriere::enBas) barriere.setCommandeSensDescente();
  
  while (true) 
  {   
    // Message d'invite
    afficheur.clear();
    afficheur.affiche(0, 0, "Bienvenue...");
 
    // contrôler la présence d'un véhicule en entrée
    while (barriere.getSituation() != Barriere::enEntree);
    
     // solliciter la saisie du code
    afficheur.affiche("Bonjour", "Code ? ");

    // récupérer le bon code secret
    do {
      // si le code est incorrect, avertir l'automobiliste
      if (!clavier.codeBon(codeSecret)) afficheur.affiche("Code incorrect", "Code ? ");      
    }
    while (!clavier.isValide());

    // afficher un message précisant la bonne valeur du code
    afficheur.affiche("Code bon", "Entrez..."); 
    
    // lever la barrière pour permettre l'entrée du véhicule
    barriere.setCommandeSensMontee();
    
    // attendre que le véhicule pénètre complètement dans le parking
    while (barriere.getSituation() != Barriere::enSortie); 
    while (barriere.getSituation() == Barriere::enSortie);
    
    // descendre la barrière une fois que le véhicule est bien entré
    barriere.setCommandeSensDescente();   
  }
 
  return 0;
}
Corrections : Visualisation du résultat en html . Version pdf .

 

Choix du chapitre Héritage

Pour communiquer avec notre barrière, nous utilisons la classe Socket qui est normalement prévu pour établir un point de connexion entre deux systèmes informatiques en précisant, d'une part sa localisation au travers de sons adresse IP, et d'autre part de solliciter le service demandé sur le poste distant au travers de son numéro de port.

En réalité, cette classe Socket n'est pas spécialement adaptée pour la barrière. Elle peut être utile pour bien d'autres systèmes de communication entre un client et un serveur. Il serait peut être judicieux de prévoir une classe de communication, par exemple ServiceBarriere, qui soit adaptée plus spécifiquement au service de la barrière, avec notamment le respect du protocole associé.

Nous pouvons utiliser le mécanisme d'héritage pour cela. Je rappelle, que l'héritage permet soit de généraliser un concept ou au contraire de spécialiser une classe déjà existante. C'est cette deuxième approche qui nous intéresse ici. Dans ce cadre là, lorsque j'hérite de la classe Socket, je récupère toutes ses compétences, auxquelles je vais rajouter dans ma classe fille ServiceBarriere, de nouvelle spécificités qui seront plus en rapport avec le service de la barriere.

Ainsi, vous allez développer la classe ServiceBarriere, avec comme spécificité :

  1. Elle hérite de la classe Socket.
  2. Elle doit posséder un constructeur qui récupère l'adresse IP désirée, et qui permet ainsi de choisir la borne d'accès sur laquelle nous désirons dialoguer.
  3. Elle doit posséder un attribut addresseIP, qui mémorise la localisation choisie.
  4. Elle doit posséder une méthode getAdresseIP() qui permet de restituer à tout moment le choix de la barrière.
  5. Elle doit enfin redéfinir le destructeur, de telle sorte que lorsque l'objet correspondant est détruit, il y ait automatiquement l'envoie du message "fin", qui respecte ainsi le protocole prévu et qui surtout permet de clôturer proprement la session en libérant les ressources côté serveur, afin que d'autres clients puissent se connecter. Le fait que nous utilisions un destructeur à ces fins, nous permet d'éviter d'écrire systématiquement cet envoie de message "fin" sur chaque programme client, d'où l'intérêt.
A titre indicatif, et pour vérification, voici le code client qui permet de lever ou d'abaisser la barrière :
#include <ServiceBarriere.h>
#include <afficheur.h>
#include <barriere.h>

int main() 
{         
  ServiceBarriere service("172.20.3.73");
  Barriere barriere(service);
  // descendre la barrière si elle se trouve en position haute
  if (barriere.getPosition() == Barriere::enHaut) {
    cout << "La barrière est en haut" << endl;
    cout << "La barrière descent" << endl;
    barriere.setCommandeSensDescente();
    cout << "La barriere est en bas" << endl;
  }
  // monter la barrière si elle se trouve en position basse
  else {
    cout << "La barrière est en bas" << endl;
    cout << "La barrière monte" << endl;
    barriere.setCommandeSensMontee();
    cout << "La barriere est en haut" <<  endl;  
  }
  
  // arrêter la communication
  cout << "Programme terminé : " << endl;
 
  return 0;
}
Voici, le résultat obtenu, côté serveur, avec l'analyse des commandes et la prise en compte de la clôture de la session

Corrections : Visualisation du résultat en html . Version pdf .

 

Choix du chapitre Gestion d'un parking privé

Pour conclure cette série de TP, je vous en propose un dernier qui permet de gérer un parking privé en rapport au nombre de places disponibles. Cette fois-ci, nous devons contrôler l'entrée, avec la saisie du code d'accès, et la sortie des véhicules. Un affichage adapté indique le nombre de places disponibles ou bien "PARKING COMPLET". Dans ce TP, vous allez juste faire une fusion des différentes recherches que nous avons déjà établie lors des TPs précédents.

Elaboration de la classe Parking

Pour que cela soit facile de mise en oeuvre, je vous propose de fabriquer une classe spécifique qui s'occupe de la gestion complète du parking. Voici les différents critères que vous devez prendre en compte :

Attributs de la classe Parking
  1. service : (ServiceBarriere) - cet attribut permet de rentrer en communication avec la vrai borne d'accès.
  2. afficheur : (Afficheur) - afficheur de la vrai borne d'accès.
  3. clavier : (Clavier) - clavier de la vrai borne d'accès.
  4. barriere : (Barriere) - barriere de la vrai borne d'accès.
  5. codeSecret : (const int) - code d'accès au parking privé.
  6. nombrePlace : (const int) - nombre de place maximum du parking privé.
  7. disponible : (int) - nombre de place actuellement disponible.
Méthodes de la classe Parking
  1. Parking(int places, int code) : Constructeur qui initialise l'ensemble des attributs. Au moment de la création, vous devez indiquer le nombre de place du parking privé, ainsi que son code d'accès.
  2. void run() : Cette méthode publique, va tout simplement s'occuper de la gestion du parking et fonctionne indéfiniment. Cette méthode fait appel aux trois autres méthodes privées que sont, affichage(), entrer() et sortir().
  3. void affichage() : Cette méthode privée s'occupe exclusivement de l'afficheur de la borne d'accès. Elle doit afficher, suivant la disponibilité des places :
    --- "PARKING COMPLET" sur la première ligne de l'afficheur si aucune place n'est disponible.
    --> "BONJOUR" sur la première ligne, et "1 place disponible" sur la deuxième ligne si le parking privé ne dispose que d'une seule place.
    --> "Bonjour" sur la première ligne, et "X places disponibles" sur la deuxième ligne dans tous les autres cas (X étant le nombre de places disponibles).
  4. void entrer() : Cette méthode privée correpond à l'algorithme que nous avons déjà mis en place pour pénétrer dans un parking privé avec saisie du code d'accès.
  5. void sortir() : Cette méthode privée correspond à l'algorithme que nous avons déjà mis en place pour sortir du parking privé.

La déclaration de la classe Parking et la définition des méthodes associées seront décrites dans le fichier en-tête "Parking.h". Les méthodes seront donc toutes "inline" même si normalement, elles devraient être des méthodes classiques.

Programme client principal avec création de l'objet parking
#include "./Parking.h"

int main() 
{         
  Parking parking(3, 7485);
  parking.run();
 
  return 0;
}

Corrections : Visualisation du résultat en html . Version pdf .
.

Dans cette gestion, nous aurions pu faire en sorte que la barrière puisse être levée par le gardien pour laisser un libre accès temporaire, ou au contraire empêcher aux automobilistes de rentrer (tout en laissant la possibilité de sortir). Pour que nous puissions mettre en oeuvre ces nouvelles possibilités, il faudrait une IHM sous forme de fenêtre et de boutons spécifiques. Cette partie sera traitée en 2ème année.