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é.
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) :
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.
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 :
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.
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.
... Dekma-Rep : 172.20.3.77
... Dekma-Park : 172.20.3.73
Analyse d’un passage, ici une sortie :
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.
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" :
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.
.
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.
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 :
Voici ci-dessous l'ensembles des méthodes que le service est capable de gérer pour traiter le programme souhaité :
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 :
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 :
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é.
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.
Le fichier Socket.h doit être placé dans le répertoire /usr/include/c++/4.2/
§
#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.
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 :
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.
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 &.
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 :
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.
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"
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.
Le programme doit fonctionner indéfiniment. Attention, l'affichage doit être parfaitement lisible sans clignotement intempestif et sans caractères supplémentaires aléatoires.
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.
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.
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.
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 :
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 :
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.
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.
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 :
Voici ci-dessous l'ensemble des fonctions à implémenter, avec leur signature respective :
A titre d'exemple, reprenons le premier TP de gestion de l'afficheur de la barrière. Je rappelle l'intitulé :
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.
.
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 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 :
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.
Le programme doit fonctionner indéfiniment. Attention, l'affichage doit être parfaitement lisible sans clignotement intempestif et sans caractères supplémentaires aléatoires.
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.
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.
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.
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 :
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 :
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 :
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.
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 :
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.
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.
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 .
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.
.
#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; }
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 :
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.
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 :
#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.
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 :
#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; }
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.
#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; }
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é :
#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; }
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.
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 :
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.
#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.