LOCODUINO

Forum de discussion
Dépôt GIT Locoduino

jeudi 19 janvier 2017

32 visiteurs en ce moment

Un gestionnaire en C++ pour votre réseau (1)

Première partie : Aiguilles et Zones

. Par : Pierre59

On va, dans cette série d’articles, développer pas à pas le noyau d’un programme de gestion de réseau en utilisant la programmation orientée objet en C++.

La programmation orientée objet peut se résumer en trois mots : encapsulation, héritage, polymorphisme. Il faut faire les trois choses pour vraiment faire de la programmation objet.

L’accent sera essentiellement mis sur la conception objet, les exemples seront donc écrits dans un C++ simplifié. Cela reste du vrai C++, mais pour pouvoir le compiler, il faudra ajouter quelques trucs techniques qui polluent un peu le programme et qui nuisent un peu à sa compréhension. On trouvera en fin d’article le programme C++ complet compilable avec aussi plus d’exemples.

Pour réaliser un programme de gestion de réseau à partir de ce noyau, il faut écrire des classes (en suivant les modèles joints), déclarer des objets, choisir les options désirées et écrire toute la partie d’interface avec le matériel : commande des aiguilles, des alimentations (analogiques ou digitales), ou des signaux, rétro-signalisation, gestion du TCO, gestion des bus, …

Pour bénéficier de toutes les possibilités du noyau, toutes les actions de l’utilisateur (commande d’aiguilles, d’itinéraires, de sens et vitesse des trains, ...) doivent êtres contrôlées par le programme avant leur application.

Ce noyau objet est très puissant et très facilement modifiable. On peut modifier le réseau facilement, sans tout remettre en cause dans le programme. On peut ajouter progressivement, suivant ses envies, des "gadgets" comme le suivi des trains (par exemple, afficher le nom du train sur le TCO, mais aussi le contrôle des sons pour les machines sonores, ..), la poursuite en canton (appelée aussi conduite sélective, elle concerne principalement le fonctionnement en analogique, un train étant contrôlé sur tout le réseau avec la même souris par commutation automatique des alimentations sur les zones), le cabsignal (l’affichage des signaux en cabine de conduite des machines, par exemple sur la souris qui affiche le signal réel que voit le train), etc …

Ce noyau objet est plutôt orienté vers la gestion de réseaux avec commande par itinéraires, signalisation, rétro-signalisation, circulation manuelle et automatique, .. ceci quelque soit leur taille. Mais il peut être aussi utilisé sur des réseaux simples en laissant tomber les parties optionnelles.

Comme le noyau est écrit en C++, il peut être utilisé avec un Arduino, un mini PC (genre PCduino) ou un vrai PC. Un Arduino UNO sera très vite insuffisant au niveau mémoire vive voire au niveau mémoire flash, un méga ou mieux un due est plus réaliste.

Première étape : l’encapsulation

La première chose à faire en programmation objet, c’est d’identifier les objets potentiels.
Dans notre cas, ceux ci ne manquent pas : aiguilles, zones, cantons, signaux, trains, TCOs, souris, …

Ensuite il faut écrire des classes pour chacun des objets retenus. Dans ce premier article on va s’intéresser aux objets aiguille et zone.

Les classes sont des modèles pour fabriquer des objets, à partir d’une classe on peut fabriquer autant d’objets que l’on veut, ces objets sont appelés instances de la classe. Par exemple si on a 10 aiguilles on fera 10 instances de la classe ’aiguille’ pour avoir 10 objets, un pour chaque aiguille.

On va commencer avec la classe aiguille, pour cela on écrit une classe en C++ :

  1. class Aiguille {};

Dans une classe les variables sont souvent appelées "attributs" et les fonctions "méthodes".

Il faut maintenant remplir la classe. Pour cela il faut se demander quelles sont les informations que l’on a besoin de savoir sur une aiguille et quelles sont les actions que l’on veut effectuer sur une aiguille.

Pour les informations, on a besoin de savoir si l’aiguille est en position directe ou en position déviée.
On peut faire cela avec deux méthodes à résultat booléen : directe() et deviee(), l’état de l’aiguille étant mémorisé dans une variable booléenne : état.

Pour les actions, on a besoin de manœuvrer l’aiguille, cela peut se faire avec deux méthodes : directer() et devier(). Ces méthodes ne font la manœuvre que si nécessaire, elles renvoient un booléen pour savoir comment cela s’est passé (true=OK). Bien évidemment ces méthodes changent l’état. La méthode : manoeuvrer() fera la commande effective (voir plus loin).

Cela donne :

  1. class Aiguille {
  2. booleen etat; // directe (true) ou deviee (false)
  3.  
  4. boolean directe() { return etat; } // méthode d'acces
  5. boolean deviee() { return !etat; } // méthode d'acces
  6.  
  7. boolean directer() {
  8. if (directe()) return true;
  9. etat=true;
  10. return manoeuvrer(true);
  11. }
  12. boolean devier() {
  13. if (deviee()) return true;
  14. etat=false;
  15. return manoeuvrer(false);
  16. }
  17. };

Pour rendre la classe utilisable, il faut une méthode : manoeuvrer() qui va faire l’interface avec le matériel. Cette méthode a besoin d’un numéro pour pouvoir commander le matériel (directement ou par un bus). On va donc rajouter à la classe un entier (ou plusieurs si besoin) qui est une sorte de numéro d’aiguille. La méthode : manoeuvrer() utilisera ce numéro pour manœuvrer effectivement l’aiguille. Cette méthode devra être complétée par l’utilisateur. Ce numéro d’aiguille va être donné en utilisant un constructeur.

Reste un problème d’initialisation de l’état. Deux cas se présentent, si on peut connaitre la position des aiguilles à l’initialisation du programme de gestion (dans le setup() ) alors on appelle la méthode : init1(), sinon il faut appeler la méthode : init2() qui force la position de l’aiguille en la manœuvrant.

On va profiter de la présence du constructeur pour ajouter une information optionnelle qui est la zone dans laquelle est l’aiguille. Cela peut être utile pour réaliser un PRS (Poste à Relais à transit Souple) où on a besoin de savoir si l’aiguille est manœuvrable (si la zone correspondante n’est pas occupée), la méthode : manoeuvrable() est prévue pour cela.

De façon générale les objets seront manipulés par le biais de pointeurs. Cela permet de profiter des mécanismes objets que nous utiliserons par la suite. Manipuler les objets (ou les structures) avec des pointeurs c’est normal en C++, il existe d’ailleurs un opérateur( -> ) prévu pour cela.

Ce qui donne :

  1. class Aiguille {
  2. boolean etat; // directe (true) ou déviée (false)
  3. int no; // numéro de l'aiguille
  4. Zone* zone; // la Zone ou est l'aiguille (optionnel)
  5.  
  6. Aiguille(int n) { no=n; } // constructeur mini
  7. Aiguille(int n,Zone* z) { no=n; zone=z; } // constructeur maxi
  8.  
  9. boolean directe() { return etat; } // méthode d’accès
  10. boolean deviee() { return !etat; } // méthode d’accès
  11.  
  12. boolean directer() {
  13. if (directe()) return true;
  14. etat=true;
  15. return manoeuvrer(true);
  16. }
  17.  
  18. boolean devier() {
  19. if (deviee()) return true;
  20. etat=false;
  21. return manoeuvrer(false);
  22. }
  23.  
  24. boolean manoeuvrable() { return zone->libre(); } // (optionnel)
  25.  
  26. boolean manoeuvrer(boolean e) { // commande de l'aiguille avec le numéro
  27. return true; // return false si cela c'est mal passe
  28. }
  29.  
  30. void init1(boolean e) { etat=e; }
  31. void init2(boolean e) { if (e) commander(e); else commander(!e); etat=e; }
  32. };

Voici deux exemples d’aiguilles (instanciations) :

  1. Aiguille* a1=new Aiguille(1); // aiguille N°1
  2. Aiguille* a2=new Aiguille(2, z9); // aiguille N°2 dans la zone z9

Maintenant passons à la classe Zone.

Comme pour Aiguille, la zone a deux états : libre et occupé, un booléen convient donc.

A l’instar d’Aiguille, on va avoir deux méthodes, libre() et occupee() pour tester l’état. Deux méthodes occuper() et liberer(), pour modifier l’état, ces deux méthodes doivent êtres appelées par la rétro-signalisation, une table de pointeurs sur les zones facilitera la tâche.
Ces deux méthodes appellent les méthodes actions() et desactions() qui sont destinées à faire les actions spécifiques à une zone particulière à l’occupation de la zone particulière et à sa libération.

  1. class Zone {
  2. boolean etat; // libre (false) ou occupé (true)
  3.  
  4. boolean occupee() { return etat; } // méthode d’accès
  5. boolean libre() { return !etat; } // méthode d’accès
  6.  
  7. void occuper() { // appelée par la rétro-signalisation
  8. // fait tout ce qu'il y a à faire en cas d'occupation (actions communes à toutes les zones)
  9. actions(); // fait les actions spécifiques à une zone
  10. }
  11.  
  12. void liberer() { // appelée par la rétro-signalisation
  13. // fait tout ce qu'il y a à faire en cas de libération (actions communes à toutes les zones)
  14. desactions(); // fait les actions spécifiques à une zone
  15. }
  16.  
  17. void actions() {} // les actions spécifiques à faire en cas d'occupation
  18. void desactions() {} // les actions spécifiques à faire en cas de libération
  19.  
  20. void init(boolean e) { etat=e; }
  21. };

Les deux méthodes : occuper() et liberer(), font tout ce qu’il y a faire lors de l’occupation d’une zone et de sa libération. Pour l’instant c’est presque vide mais il faudra faire la mise à jour du TCO, le suivi des trains, le cabsignal, etc …

Ces deux méthodes font tout ce qu’il y a de commun à toutes les zones. Mais pour chaque zone il y a des choses particulières à faire : aubiner les signaux, gérer le BAL, gérer les itinéraires. … .
Cela va être le rôle de deux méthodes, la méthode : actions() qui fait les actions à faire lors de l’occupation et : desactions() lors de la libération.

Comme pour aiguille il reste un problème d’initialisation de l’état. A l’initialisation du programme de gestion (dans le setup() ) la rétro-signalisation doit appeler, pour chaque zone, la méthode : init() pour indiquer l’état initial de la zone.

Deuxième étape : l’héritage

Bien que complète la classe zone n’est pas utilisable, d’autant que les méthodes : actions() et : desactions() ne font rien.
Lors de l’occupation et de la libération d’une zone, outre des actions communes à toutes les zones, en pratique il y a des actions spécifiques à chaque zone à faire : aubiner les signaux, gérer le BAL, gérer les itinéraires. …
C’est le rôle des deux méthodes : actions() et : desactions(). Pour cela on pourrait utiliser deux pointeurs sur des fonctions passés en paramètres du constructeur, et utilisés dans ces méthodes mais il y a une façon plus naturelle en programmation objet, c’est l’héritage.

Pour chaque zone du réseau on va écrire une classe spécifique qui héritera de la classe de base Zone et qui va redéfinir les méthodes : actions() et : desactions() pour leur faire faire les actions propres à chaque zone.

  1. class Z8 : Zone { // héritage de Zone
  2. void actions() {} // les actions spécifiques à faire en cas d'occupation
  3. void desactions() {} // les actions spécifiques à faire en cas de libération
  4. };
  5.  
  6. class Z9 : Zone { // héritage de Zone
  7. void actions() {} // les actions spécifiques à faire en cas d'occupation
  8. void desactions() {} // les actions spécifiques à faire en cas de libération
  9. };

Pour faire des choses plus intéressantes il faut enrichir la classe. Deux choses sont bien utiles, d’une part les deux signaux éventuels implantés sur la zone. D’autre part l’accès à la zone suivante et à la zone précédente.

Pour les signaux c’est facile, on ajoute deux variables de type pointeur de signal (on verra les signaux dans un prochain article), un constructeur à deux paramètres (pointeur de signal) et deux méthodes d’accès, les sens sont notés comme à la SNCF (pair et impair), en l’absence d’un signal on met NULL (le pointeur vide) :

  1. class Zone { // les ajouts a la classe zone
  2. Signal* signalPair; // les signaux éventuels de la zone
  3. Signal* signalImpair;
  4.  
  5. Zone(Signal* sp,Signal* si) { // constructeur
  6. signalPair=sp;
  7. signalImpair=si;
  8. }
  9.  
  10. Signal* signalPair() { return signalPair; } // méthode d'accès
  11. Signal* signalImpair() { return signalImpair; } // méthode d'accès
  12. };

Pour les zones suivantes et précédentes on pourrait aussi envisager de les passer au constructeur mais dans notre cas deux problèmes empêchent d’avoir recours à cette technique.

- Le premier problème est que les zones s’appellent mutuellement : Par exemple, si on a deux zones z1 et z2 la suivante paire de z1 peut être z2 et la suivante impaire de z2 être z1, c’est impossible à déclarer, car il faudrait déclarer z1 avant z2 et z2 avant z1 !
- Le deuxième problème vient du fait que les zones suivantes dépendent éventuellement de la position d’une (ou plusieurs) aiguille. Cela reflète l’aspect dynamique du réseau et cet aspect est essentiel pour la modélisation.

On ne peut donc pas passer par le constructeur directement. On pourrait envisager de passer des pointeurs sur des fonctions, mais il est plus pratique d’utiliser l’héritage d’autant plus que l’on a déjà commencé.

On va donc pour chaque zone réelle compléter la classe spécifique déjà écrite en lui ajoutant deux méthodes : suivantePaire() et : suivanteImpaire(). Ces deux noms de méthode sont préférés aux noms suivante() et precedente() car il ne dépendent pas du sens de circulation.

Pour que l’héritage et les mécanismes objets se passent bien en C++ (on en parlera dans le prochain article), il faut ajouter aussi ces méthodes à la classe de base Zone. Il faut aussi, pour toutes les méthodes qui sont redéfinies ainsi, mettre le mot clé virtual devant les méthodes (actions(), desactions(), suivantePaire(), suivanteImpaire() ).

Il ne reste plus qu’a tout réunir, la classe de base zone :

  1. class Zone {
  2. boolean etat; // libre (false) ou occupe (true)
  3. Signal* signalPair; // les signaux éventuels de la zone
  4. Signal* signalmpair;
  5.  
  6. Zone(Signal* sp,Signal* si) { // constructeur
  7. signalPair=sp;
  8. signalImpair=si;
  9. }
  10.  
  11. boolean occupee() { return etat; } // méthode d’accès
  12. boolean libre() { return !etat; } // méthode d’accès
  13.  
  14. void occuper() { // appelée par la rétrosignalisation
  15. // fait tout ce qu'il y a à faire en cas d'occupation
  16. // (actions communes à toutes les zones)
  17. actions(); // fait les actions spécifiques à une zone
  18. }
  19.  
  20. void liberer() { // appelée par la rétrosignalisation
  21. // fait tout ce qu'il y a à faire en cas de libération
  22. // (actions communes à toutes les zones)
  23. desactions(); // fait les actions spécifiques à une zone
  24. }
  25.  
  26. virtual void actions() {} // les actions spécifiques à faire en cas d'occupation
  27. virtual void desactions() {} // les actions spécifiques à faire en cas de libération
  28.  
  29. Signal* signalPair() { return signalPair; } // méthode d'acces
  30. Signal* signalImpair() { return signalImpair; } // méthode d'acces
  31.  
  32. virtual Zone* suivantePaire() { return NULL; } // la zone suivante paire (éventuellement vide)
  33. virtual Zone* suivanteImpaire() { return NULL; } // la zone suivante impaire (éventuellement vide)
  34.  
  35. void init1(boolean e) { etat=e; } // initialisation
  36.  
  37. Zone* selonAiguille(Aiguille* a,Zone* z1,Zone* z2) { // méthode utilitaire
  38. return a->directe()?z1:z2;
  39. }
  40. };

La méthode utilitaire selonAiguille() de la classe zone permet de prendre en compte l’aspect dynamique du réseau en fonction de la position réelle des aiguilles, cet aspect est essentiel au fonctionnement du gestionnaire de réseau.

Exemples de zones réelles (les exemples sont là pour montrer tous les cas caractéristiques d’utilisation, mais ils ne correspondent à aucun réseau concret) :

  1. class Z8 : Zone { // héritage de Zone
  2. Z8(Signal* sp,Signal* si):Zone(sp,si) {} // constructeur
  3.  
  4. virtual void actions() { c2.aubiner(); }
  5. // les actions spécifiques à faire en cas d'occupation
  6. virtual void desactions() { }
  7. // les actions spécifiques à faire en cas de libération
  8. virtual Zone* suivantePaire() { return NULL; }
  9. //pas de zone suivante paire
  10. virtual Zone* suivanteImpaire() { return z9; }
  11. // la zone suivante impaire
  12. } ;
  13.  
  14. class Z9 : Zone { // héritage de Zone
  15. Z9(Signal* sp,Signal* si):Zone(sp,si) {} // constructeur
  16.  
  17. virtual void actions() {s2.aubiner(); }
  18. // les actions spécifiques à faire en cas d'occupation
  19. virtual void desactions() {}
  20. // les actions spécifiques à faire en cas de libération
  21. virtual Zone* suivantePaire() { return selonAiguille( a1,z8,z9 }
  22. // la zone suivante paire depends de a1
  23. virtual Zone* suivanteImpaire() { return selonAiguille( a1,selonAiguille( a2,z8,z9),z9; }
  24. // la zone suivante impaire depend de a1 et a2
  25. };

Exemples de déclarations d’objets signaux, zones, aiguilles :

  1. Signal* c2=new C2(); // signaux
  2. Signal* s2=new S2();
  3.  
  4. Zone* z8=new Z8(s2,c2); // zones
  5. Zone* z9=new Z9(s2,NULL);
  6.  
  7. Aiguille* a1=new Aiguille(1,z8); // aiguilles
  8. Aiguille* a2=new Aiguille(2,z9);
  9.  
  10. Zone* tableZones[] {z8,z9} // la table des zones pour la retrosignalisation

L’exemple précédent n’est pas directement compilable, il faut mettre des protections (mots clés : public, protected, …), il faut respecter un ordre très précis des déclarations de classes et de variables car les objets s’utilisent les uns les autres.

Les méthodes qui sont écrites complètement dans la classe sont réputées "inline", c’est à dire que le compilateur ne fait pas un vrai appel de méthode mais met les instructions de la méthode directement dans le programme. C’est très bien pour les méthodes courtes comme les méthodes d’accès ou certaines méthodes utilitaires. Pour les autres il faut juste les spécifier dans la classe et les écrire en dehors de la classe en rappelant le nom de la classe. Dans notre cas cela facilite la prise en compte des inextricables problèmes avec l’ordre des déclarations.

On trouvera ci dessous un exemple compilable plus complet, on a rajouté tout ce qu’il faut pour que cela soit compilable et ajusté l’ordre des déclarations qu’il faut scrupuleusement respecter pour que cela reste compilable.

On a aussi diversifié les exemples, ajouté la possibilité de donner des noms et des types aux objets avec les constructeurs nécessaires. Il y a aussi des choses (variables et méthodes) qui seront utilisées dans les prochains articles.

Zip - 8.2 ko
Noyau de base Gestionnaire C++

Dans les articles suivant on verra les classes Signal, Train, Itinéraire ainsi que des gadgets comme le suivi des trains, la poursuite en canton, le cabsignal, …

Si vous souhaitez tester cette première partie avec une modélisation réelle de votre propre réseau, vous pouvez suivre le fil « modélisation logicielle d’un réseau, ici : http://forum.locoduino.org/index.php?topic=72.msg781#msg781

et aussi : ici dans le forum

11 Messages

Réagissez à « Un gestionnaire en C++ pour votre réseau (1) »

Qui êtes-vous ?
Votre message

Pour créer des paragraphes, laissez simplement des lignes vides.

Lien hypertexte

(Si votre message se réfère à un article publié sur le Web, ou à une page fournissant plus d’informations, vous pouvez indiquer ci-après le titre de la page et son adresse.)

Rubrique « Projets »

Un chenillard de DEL

Enseigne de magasin

Feux tricolores

Multi-animations lumineuses

L’Arduino et le système de commande numérique DCC

Un décodeur d’accessoire DCC versatile basé sur Arduino

Un moniteur de signaux DCC

Une barrière infrarouge

Un capteur RFID

Un TCO xpressnet

Une animation sonore

L’Arduino au coeur des systèmes de pilotage analogiques ou numériques

Calcul de la vitesse d’un train miniature avec l’Arduino

La génèse d’un réseau 100% Arduino

Une horloge à échelle H0

Simulateur de soudure à arc

Un automatisme de Passage à Niveau

La rétro-signalisation sur Arduino

Décodeur pour aiguillage à solénoïdes sur Arduino

Un décodeur DCC pour les signaux à deux ou trois feux sur Arduino NANO/UNO

Etude d’un passage à niveau universel

Réalisation pratique d’un système de mesure de vitesse à l’échelle N

Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation

Un décodeur DCC pour 16 feux tricolores

Comment piloter trains et accessoires en DCC avec un Arduino (1)

Comment piloter trains et accessoires en DCC avec un Arduino (2)

Comment piloter trains et accessoires en DCC avec un Arduino (3)

Comment piloter trains et accessoires en DCC avec un Arduino (4)

SGDD : Système de Gestion DD (1)

SGDD : Système de Gestion DD (2)

SGDD : Système de Gestion DD (3)

La PWM : qu’est-ce ? (1)

La PWM : qu’est-ce ? (2)

Mise en oeuvre du Bus CAN entre modules Arduino (1)

Mise en oeuvre du Bus CAN entre modules Arduino (2)

Un gestionnaire en C++ pour votre réseau (1)

Un gestionnaire en C++ pour votre réseau (2)

Un gestionnaire en C++ pour votre réseau (3)

Un gestionnaire en C++ pour votre réseau (4)

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)

Réalisation de centrales DCC avec le logiciel libre DCC++ (2)

Gestion d’une gare cachée (1)

Gestion d’une gare cachée (2)

Gestion d’une gare cachée (3)

Les derniers articles

Un gestionnaire en C++ pour votre réseau (4)


Pierre59

Un décodeur DCC pour 16 feux tricolores


Dominique, JPClaude, Thierry

Réalisation de centrales DCC avec le logiciel libre DCC++ (2)


bobyAndCo

Réalisation de centrales DCC avec le logiciel libre DCC++ (1)


Dominique

Une Passerelle entre le bus S88 et le bus CAN pour la rétro signalisation


JPClaude

Un gestionnaire en C++ pour votre réseau (3)


Pierre59

Réalisation pratique d’un système de mesure de vitesse à l’échelle N


Dominique

Etude d’un passage à niveau universel


Dominique, JPClaude

Un gestionnaire en C++ pour votre réseau (2)


Pierre59

Gestion d’une gare cachée (3)


Jean-Luc

Les articles les plus lus

La PWM : qu’est-ce ? (1)

Mise en oeuvre du Bus CAN entre modules Arduino (2)

Comment piloter trains et accessoires en DCC avec un Arduino (3)

Une barrière infrarouge

L’Arduino au coeur des systèmes de pilotage analogiques ou numériques

Réalisation de centrales DCC avec le logiciel libre DCC++ (2)

Calcul de la vitesse d’un train miniature avec l’Arduino

L’Arduino et le système de commande numérique DCC

La rétro-signalisation sur Arduino

Comment piloter trains et accessoires en DCC avec un Arduino (1)