LOCODUINO

Aide
Forum de discussion
Dépôt GIT Locoduino
Flux RSS

jeudi 8 juin 2023

15 visiteurs en ce moment

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

Deuxième partie : Signaux

. Par : Pierre59

DIFFICULTÉ :

Après les classes aiguille et zone on va aborder la classe signal ou plutôt les classes signaux. Les signaux sont très polymorphes (il y a beaucoup de variétés) et leur mode de fonctionnement est très varié. L’héritage, et le polymorphisme qui en découle, vont permettre de prendre en compte relativement facilement les variantes rencontrées dans la signalisation SNCF réelle. Cela va permettre aussi de monter les mécanismes du polymorphisme, et il y en a de très subtils. Il ne faut pas s’inquiéter, ces mécanismes du polymorphisme font, automatiquement, ce qu’on aimerait bien que cela fasse et même au delà des espérances.

On distinguera ici deux catégories de signaux, ceux sans BAL (Bloc Automatique Lumineux) et ceux avec BAL.
Pour pouvoir mélanger des signaux avec et sans BAL (utile pour les réseaux ayant des parties avec BAL et des parties sans) le fonctionnement des deux catégories sera identique, on aura juste des méthodes pour ouvrir ou fermer les signaux, tout le reste sera automatique, c’est à dire que le calcul des feux et la présentation des cibles pour les signaux mécaniques sera fait automatiquement en fonction de l’environnement (autres signaux, aiguilles, zones, …). L’aubinage est aussi pris en compte. L’aubinage est la fermeture automatique d’un signal lors de son franchissement par un train (le nom vient de l’invention, par Mr Aubine, de la pédale mécanique qui fermait les signaux mécaniques au passage d’un train, et le nom est resté pour les signaux lumineux) .

Des objets signaux sont nécessaires dans le gestionnaire pour assurer la sécurité, c’est par leur biais que les arrêts et ralentissements des trains sont gérés. Par contre les signaux réels ne sont là que pour faire joli, mais ils sont pris en compte (aussi bien les lumineux que les mécaniques).

Les signaux lumineux peuvent présenter différents feux, en voici quelques uns (avec les abréviations utilisées) :

  • voie libre (Vl) : feu vert
  • avertissement (A) : feu jaune
  • sémaphore (S) : feu rouge
  • carré © : deux feux rouges
  • carré violet (Cv) : feu violet
  • manœuvre (M) : feu blanc
  • ralentissement ® : deux feux jaunes horizontaux
  • rappel de ralentissement (RR) : deux feux jaunes verticaux
  • disque (D) : un feu jaune et un feu rouge

Certains feux peuvent êtres clignotants (Vlc,Ac,Sc,Mc,Rc,RRc). Certains signaux peuvent aussi présenter un petit feu blanc appelé œilleton.

Les signaux mécaniques peuvent présenter différentes cibles, en voici quelques unes (avec les abréviations utilisées) :

En plus des cibles, les signaux mécaniques présentent aussi des feux ; quand aucune cible n’est présentée le feu est à voie libre (Vl).

Signaux sans BAL

Commençons par la première catégorie, les signaux sans BAL. Il y a pas mal de variété dans cette catégorie, voici une liste non exhaustive (avec les feux) :

  • carre violet (Cv M)
  • avertissement (A Vl)
  • avertissement avec ralentissement (A Vl R)
  • carré (C Vl)
  • carré avec avertissement (C A Vl)
  • carre avec rappel de ralentissement (C Vl RR)
  • carre avec avertissement et rappel de ralentissement (C A Vl RR)
  • sémaphore (S Vl)
  • disque (D Vl)

Ces signaux pouvant êtres mécaniques ou lumineux. Les carrés avec « avertissement » réalisent l’avertissement du carré suivant.

On va détailler ici les classes pour quelques signaux manuels (on en trouvera d’autres dans les fichiers joints). Ces signaux sont judicieusement choisis pour illustrer les mécanismes de l’héritage et du polymorphisme, tout en montrant la facilité de mise en oeuvre (je n’ose imaginer comment on ferait en programmation classique !).

Comme pour la classe zone on va écrire une classe signal qui regroupe tout ce qui est commun à tous les signaux. Il faut commencer par coder les feux, il y a beaucoup de feux différents. Une façon bien pratique est d’écrire une énumération, en s’arrangeant pour que chaque feu soit codé sur un bit, le numéro de chaque feu est alors une puissance de 2 (1, 2, 4, 8, 16, 32, 64, …) qui peut aussi s’écrire en utilisant l’opérateur de décalage à gauche (1<<0, 1<<1, 1<<2, 1<<3, 1<<4, …), ceci pour faciliter l’utilisation :

  1. enum { E=0, // eteint
  2. Vl=1<<0, A=1<<1, S=1<<2, C=1<<3, R=1<<4, RR=1<<5, M=1<<6, Cv=1<<7,
  3. Vlc=1<<8, Ac=1<<9, Sc=1<<10, Rc=1<<11, RRc=1<<12, Mc=1<<13, D=1<<14, X=1<<15
  4. };

Il est prévu presque tous les feux (SNCF), même les clignotants (avec un petit c). Il y a 15 feux possibles plus un libre (X), cela tient dans un type int. Pour la portabilité on déclare un type spécial sur 16 bits donc un int pour l’Arduino :

  1. typedef unsigned int typeFeux; // 16 cas possibles ( 32 avec un long )

Outre les feux présentés par le signal, on a besoin d’un entier pour l’interface avec l’électronique (commande des feux et/ou des moteurs), plus en option un nom (pour la mise au point) et un type (pour la mise au point et pour distinguer éventuellement les mécaniques des lumineux, ou d’autres usages). Cela donne :

  1. class Signal {
  2. typeFeux feux; // les feux
  3. int no; // numero du signal
  4. char* nom; // le nom du signal OPTION
  5. int type; // le type du signal OPTION
  6. };

Il faut ensuite une méthode d’accès et des méthodes de test :

  1. typeFeux getFeux() { return feux; } // obtention des feux
  2. boolean ferme() { return feux&(S|C|Cv); } // test signal ferme
  3. boolean ouvert() { return !ferme(); } // test signal ouvert

Il faut aussi des méthodes pour ouvrir et fermer les signaux ainsi que les aubiner :

  1. virtual void ouvrir() {} // ouverture manuelle
  2. virtual void fermer() {} // fermeture manuelle
  3. virtual void aubiner() { fermer(); } // fermeture automatique (retrosignalisation)

Ces méthodes seront redéfinies (si besoin) dans les classes des signaux particuliers (d’où le mot clé virtual).

Pour gérer les ralentissements (à 30 ou 60km/h ) il faut des méthodes :

  1. virtual boolean ralentissement30() { return false; } // true si ralentissement a 30
  2. virtual boolean ralentissement60() { return false; } // true si ralentissement a 60

Ces méthodes seront redéfinies (si besoin) dans les classes des signaux particuliers.

Pour assurer l’automatisme du calcul des feux, il faut pouvoir, en général, accéder aux signaux précédents et suivants. Il faut aussi, quand un signal est manœuvré (manuellement ou automatiquement), qu’il avertisse le précédent pour qu’il mette à jour ses feux, c’est le rôle de la méthode majFeux() (Mise A Jour Feux) :

  1. virtual Signal* suivant() { return NULL; } // le signal suivant
  2. virtual Signal* precedent() { return NULL; } // le signal precedent
  3.  
  4. virtual void majFeux() {} // appelee par le signal suivant

Ces méthodes seront redéfinies (si besoin) dans les classes signaux particulières.

Pour finir il faut une méthode pour manœuvrer le signal, cette méthode fait l’interface avec l’électronique (leds, moteurs, …). Elle doit être complétée par l’utilisateur (en utilisant la variable no) :

  1. void manoeuvrer() {} // manoeuvre du signal

Cette méthode peut aussi gérer la zone d’arrêt (si elle existe).

La classe Signal comporte aussi quelques méthodes utilitaires et des méthodes en option.

Voici la classe Signal complète :

  1. typedef unsigned int typeFeux; // 16 cas possibles ( 32 avec long )
  2.  
  3. // les feux possibles
  4. enum { E=0, // eteint
  5. Vl=1<<0, A=1<<1, S=1<<2, C=1<<3, R=1<<4, RR=1<<5, M=1<<6, Cv=1<<7,
  6. Vlc=1<<8, Ac=1<<9, Sc=1<<10, Rc=1<<11, RRc=1<<12, Mc=1<<13, D=1<<14, X=1<<15
  7. };
  8.  
  9. enum {MECANIQUE,LUMINEUX,BAS,BAL, ... }; // des types de signaux
  10.  
  11. class Signal {
  12. typeFeux feux;
  13. int no; // numero du signal
  14. char* nom; // le nom du signal OPTION
  15. int type; // le type du signal OPTION
  16.  
  17. Signal(typeFeux f,int n) { feux=f; no=n; } // constructeur mini
  18. Signal(int t,typeFeux f,int n) { feux=f; no=n; } // constructeur
  19. Signal(char* nm,int t,typeFeux f,int n) { nom=nm; type=t; feux=f; no=n; } // constructeur maxi
  20.  
  21. typeFeux getFeux() { return feux; } // methode d'acces
  22.  
  23. boolean ferme() { return feux&(S|C|Cv); } // methode de test
  24. boolean ouvert() { return !ferme(); } // methode de test
  25.  
  26. virtual void ouvrir() {} // ouverture manuelle
  27. virtual void fermer() {} // fermeture manuelle
  28. virtual void aubiner() { fermer(); } // fermeture automatique (retrosignalisation)
  29.  
  30. void manoeuvrer() { ... } // manoeuvre effective du signal
  31.  
  32. virtual boolean ralentissement30() { return false; } // true si ralentissement a 30
  33. virtual boolean ralentissement60() { return false; } // true si ralentissement a 60
  34.  
  35. virtual void majFeux() {} // mise a jour du signal precedent
  36.  
  37. virtual Signal* suivant() { return NULL; } // le signal suivant
  38. virtual Signal* precedent() { return NULL; } // le signal precedent
  39.  
  40. Signal* selonAiguille(Aiguille* a,Signal* s1,Signal* s2) { return a->directe()?s1:s2; }
  41. };

La méthode utilitaire selonAiguille() facilite le choix des signaux suivants. On trouvera aussi dans les fichiers joints des méthodes pour gérer la présentation des cibles pour les signaux mécaniques.

Quelques signaux sans BAL

Avec cette classe de base, on peut maintenant écrire quelques classe de signaux particuliers.

Carré violet

Commençons par un carré violet, c’est le signal le plus simple car il ne dépend pas d’autres signaux :

  1. class CarreViolet:Signal {
  2. CarreViolet(int n):Signal(Cv,n) {} // constructeur mini
  3. CarreViolet(char* nm,int t,int n):Signal(nm,t,Cv,n) {} // constructeur maxi
  4.  
  5. virtual void ouvrir() { feux=M; manoeuvrer(); }
  6. virtual void fermer() { feux=Cv; manoeuvrer(); }
  7. };

Cette classe hérite de la classe Signal. Deux constructeurs sont proposés, un constructeur minimum (juste ce qui est indispensable) et un constructeur maximum (avec les options : nom et type), l’utilisateur peut adapter les constructeurs en fonction de ses besoins.

Les méthodes redéfinies ouvrir() et fermer() modifient l’état du signal en deux phases :
- calcul des feux (ici c’est facile)
- manœuvre du signal (changement des allumages de leds et/ou commande des moteurs, contrôle de la zone d’arrêt).

En pratique (voir le fichier joint) un test pour savoir si le signal est déjà dans la bonne position est ajouté.

Cette classe peut être instanciée directement, pour chaque carré violet nécessaire :

  1. Signal* cv3=new CarreViolet("Cv3",BAS,1); // constructeur maxi

Avertissement

Le signal d’avertissement est un peu plus compliqué car il dépend du signal suivant qui doit être un carré ou un sémaphore. Conformément aux choix qui ont été faits, le fonctionnement de ce signal est entièrement automatique, il est lié à son carré ou sémaphore, il n’y a donc pas de méthode de manœuvre manuelle (ouvrir() ou fermer()). Le signal peut être lumineux ou mécanique, il peut être aubiné :

  1. class Avertissement:Signal {
  2. Avertissement(int n):Signal(A,n) {} // constructeur mini
  3. Avertissement(char* nm,int t,int n):Signal(nm,t,A,n) {} // constructeur maxi
  4.  
  5. virtual void aubiner() { // appelé par les méthodes actions de zone
  6. feux=A;
  7. manoeuvrer();
  8. }
  9.  
  10. virtual void majFeux() { // appelé par le signal suivant
  11. if (suivant()->ferme()) feu=A;
  12. else feu=Vl;
  13. manoeuvrer();
  14. }
  15. };

On retrouve deux variantes du constructeur, une méthode aubiner() (fermeture automatique). La méthode majFeux() est nouvelle, elle est appelée par le signal suivant lors d’un changement de celui-ci, fait le calcul des feux et la manœuvre du signal (leds et/ou moteur).

En pratique (voir le fichier joint), un test est effectué sur le suivant, s’il est NULL une erreur est déclenchée, c’est à l’utilisateur d’afficher un message. Ce test est indispensable pour éviter d’inextricables erreurs à l’exécution. Un test est aussi effectué pour n’appeler la méthode manoeuvrer() que si c’est nécessaire.

Pour utiliser cette classe, comme on le fait pour la classe Zone, il faut écrire une nouvelle classe héritant de la classe Avertissement et redéfinir la méthode suivant(), l’écriture d’un ou plusieurs constructeurs est aussi nécessaire. Exemple :

  1. class A09: Avertissement { // attention aux pins analogiques A0, A1, A2, …
  2. A09(int n):Avertissement(n) {} // constructeur mini
  3. A09(char* nm,int t,int n):Avertissement(nm,t,n) {} // constructeur maxi
  4.  
  5. virtual Signal* suivant() { return c2; }
  6. };

Exemple d’utilisation :

  1. Signal* a9=new A09("A9",MECANIQUE,11);

Carré

Le signal carré est simple :

  1. class Carre:Signal {
  2. Carre(int n):Signal(C,n) {} // constructeur mini
  3. Carre(char* nm,int t,int n):Signal(nm,t+CARRE,C,n) {} // constructeur maxi
  4.  
  5. virtual void ouvrir() { feux=Vl; manoeuvrer(); precedent()->majFeux(); }
  6. virtual void fermer() { feux=C; manoeuvrer(); precedent()->majFeux(); }
  7. };

Outre deux variantes de constructeurs, il y a deux méthodes pour manœuvrer manuellement le carré, ouvrir() et fermer(). Ces deux méthodes font la manœuvre du signal d’avertissement précédent en appelant la méthode majFeux() sur l’avertissement.

En pratique, comme pour la classe précédente, des tests sont ajoutés pour éviter des manœuvres inutiles ou des pointeurs NULL. Une méthode utilitaire (de la classe Signal) manoeuvreEtMajFeux() est utilisée pour simplifier les écritures.

Pour utiliser cette classe, toujours la même technique, écrire une classe qui hérite de la classe Carre et qui redéfinit la méthode precedent(). Exemple :

  1. class C2:Carre {
  2. C2(int n):Carre(n) {} // constructeur mini
  3. C2(char* nm,int t,int n):Carre(nm,t,n) {} // constructeur maxi
  4.  
  5. virtual Signal* precedent() { return a9; }
  6. };

Exemple d’utilisation :

  1. Signal* c2=new C2(3);

Carré avec rappel de ralentissement

Le carré avec rappel de ralentissement est une variante de carré. Tout naturellement, en programmation objet, on écrit une classe qui hérite de Carre et qui ne redéfinit que les méthodes nécessaires, ici c’est la méthode ouvrir() :

  1. class CarreRappelRalentissement:Carre {
  2. CarreRappelRalentissement(int n):Carre(n) {} // constructeur mini
  3. CarreRappelRalentissement(char* nm,int t,int n):Carre(nm,t,n) {} // constructeur maxi
  4.  
  5. virtual void ouvrir() {
  6. if (ralentissement60()) feux=RRc; else
  7. if (ralentissement30()) feux=RR;
  8. else feux=Vl;
  9. manoeuvrer();
  10. precedent()->majFeux();
  11. }
  12. };

La méthode ouvrir() fait le calcul des feux, les méthodes booléennes ralentissement60() et ralentissement30() sont héritées de Signal, elles retournent false (par défaut). Comme pour la classe Carre, pour utiliser la classe CarreRappelRalentissement il faut écrire une nouvelle classe qui redéfinit la méthode precedent() et qui redéfinit une des méthodes ralentissement60() et ralentissement30() (ou les deux). Exemple :

  1. class C3:CarreRappelRalentissement {
  2. C3(int n):CarreRappelRalentissement(n) {} // constructeur mini
  3. C3(char* nm,int t,int n):CarreRappelRalentissement(nm,t,n) {} // constructeur maxi
  4.  
  5. virtual Signal* precedent() { return c2; }
  6. virtual boolean ralentissement30() { return aig1->deviee(); } // si l'aig1 est deviee
  7. };

Exemple d’utilisation :

  1. Signal* c3=new C3(5);

Arbitrairement l’information de ralentissement est attachée au carré rappel de ralentissement. Bien évidemment le ralentissement sera affiché sur le signal précédent qui doit pouvoir présenter le ralentissement (avertissement avec ralentissement ou carre avec ralentissement).

On trouvera dans le fichier signaux.h les SNCF les plus courants sans BAL.

Signaux avec BAL

La deuxième catégorie est celle des signaux avec BAL, ce sont uniquement des signaux lumineux, pouvant tous présenter le sémaphore, l’avertissement et la voie libre. Voici un liste non exhaustive (avec les feux) :

  • sémaphore (S A Vl)
  • sémaphore avec ralentissement (S A Vl R)
  • carré (C S A Vl)
  • carre avec ralentissement (C S A Vl R)
  • carre avec rappel de ralentissement (C S A Vl RR)

On trouve aussi souvent des carrés pouvant présenter un feu de manœuvre (M) qui ne sont pas prévus ici mais que l’on pourrait facilement prendre en compte.

Pour ne pas alourdir la classe Signal une classe SignalBAL héritant de la classe Signal est écrite, cette classe comprend ce qui est spécifique aux signaux de BAL :

  1. class SignalBAL:Signal {
  2. boolean modePermanent=false; // ouverture permanente
  3.  
  4. SignalBAL(typeFeux f,int n):Signal(f,n) {} // constructeur mini
  5. SignalBAL(int t,typeFeux f,int n):Signal(t,f,n) {} // constructeur
  6. SignalBAL(char* nm,int t,typeFeux f,int n):Signal(nm,t,f,n) {} // constructeur maxi
  7.  
  8. virtual void aubiner() {} // fermeture automatique par retrosignalisation
  9. virtual void desaubiner() {} // ouverture automatique par retrosignalisation
  10.  
  11. virtual typeFeux calculFeux() { return E; } // calcul des feux
  12.  
  13. void setPermanent(boolean b) { modePermanent=b; } // ouverture permanente
  14.  
  15. virtual boolean cantonOccupe() { return false; } // occupation du "canton"
  16. };

Le gestion automatique des signaux se fait par l’appel des méthodes aubiner() et desaubiner(). Ces méthodes sont appelées par la rétrosignalisation. La méthode cantonOccupe() permet de tester l’occupation du "canton".

Pour pouvoir utiliser les méthodes de SignalBAL il faut déclarer les signaux de BAL du type SignalBAL et non Signal, sinon il y aura des erreurs de compilation.

Sémaphore

Commençons par le plus simple, le sémaphore de BAL. Le fonctionnement de ce signal est entièrement automatique sans aucune action manuelle, il n’y a donc pas de méthode ouvrir() ou fermer(), la fermeture et l’ouverture du signal se font par la rétrosignalisation, par le biais des méthodes aubiner() et desaubiner(). Le calcul des feux devant être fait dans deux méthodes, une méthode annexe calculFeux() est introduite :

  1. class SemaphoreBAL:SignalBAL {
  2. SemaphoreBAL(int n):Signal(Vl,n) {} // constructeur mini
  3. SemaphoreBAL(char* nm,int t,int n):Signal(nm,t+BAL,Vl,n) {} // constructeur maxi
  4.  
  5. virtual void aubiner() {// appelé par les methodes actions de zone
  6. feu=S; manoeuvrer(); precedent()->maj();
  7. }
  8.  
  9. virtual void desaubiner() { // appelé par les methodes des actions de zone
  10. feux=calculFeux(); manoeuvrer(); precedent()->maj();
  11. }
  12.  
  13. virtual typeFeux calculFeux() { // feux a l'ouverture
  14. if (cantonOccupe()) return S; // "canton" occupe ( precaution )
  15. if (suivant()->ferme()) return A; else return Vl;
  16. }
  17.  
  18. void majFeux() { Signal* s; typeFeux f; // appelé par le signal suivant
  19. feux=calculFeux(); manoeuvrer(); precedent()->maj(); }
  20. }
  21.  
  22. virtual boolean cantonOccupe() { return true; } // test d'occupation du "canton"
  23. };

Normalement la fermeture du signal se fait lors de l’occupation de la zone suivant le signal par la méthode aubiner() et l’ouverture se fait lors de la libération de la dernière zone du "canton" par la méthode desaubiner(). C’est la rétrosignalisation qui contrôle cela, suivant le cheminement suivant :

De même pour l’ouverture :

Pour palier les anomalies de circulation de nos trains (ruptures d’attelages, mise ou retrait de matériels sur une zone sans précautions, …), une vérification de l’occupation du "canton" est faite par le biais de la méthode cantonOccupe().

Comme d’habitude, pour un signal particulier il faut écrire une classe qui hérite de SemaphoreBAL, et qui redéfinit les méthodes nécessaires :

  1. class S5:SemaphoreBAL{
  2. S5(int n):SemaphoreBAL(n) {} // constructeur mini
  3. S5(char* nm,int t,int n):SemaphoreBAL(nm,t,n) {} // constructeur maxi
  4.  
  5. virtual Signal* suivant() { return c3; }
  6. virtual Signal* precedent() { return c2; }
  7. virtual boolean cantonOccupe() { return occupees(z8,z9); }
  8. };

Exemple d’utilisation :

  1. Signal* s5=new S5("S5",BAL,3);

Carré

Passons, comme avant dernier exemple, à un carré avec BAL. Cela peut sembler curieux à certains, mais c’est nécessaire dans les postes d’aiguillage du type PRS (Poste tout Relais à transit Souple) et dans le cas d’itinéraires permanents. Le fonctionnement de tels carrés est le suivant :

- à l’ouverture (manuelle), calcul des feux comme pour un sémaphore de BAL (S possible)
- à la fermeture (manuelle), mise au carré
- à l’aubinage (automatique par occupation du "canton"), si l’ouverture est permanente, mise au sémaphore sinon mise au carré
- au désaubinage (automatique par libération du "canton"), si l’ouverture est permanente, calcul des feux comme pour l’ouverture sinon rien à faire

On retrouve tout ce qu’il y avait dans sémaphore avec BAL, plus les ouvertures et fermetures manuelles et la gestion du mode permanent :

  1. class CarreBAL:Signal {
  2. CarreBAL(int n):Signal(C,n) {} // constructeur mini
  3. CarreBAL(char* nm,int t,int n):Signal(nm,t+BAL,C,n) {} // constructeur maxi
  4.  
  5. virtual void ouvrir() { // ouverture manuelle
  6. feux=calculFeux(); manoeuvrer(); precedent()->maj();
  7. }
  8.  
  9. virtual void fermer() { // fermeture manuelle
  10. feux=C; manoeuvrer(); precedent()->maj();
  11. }
  12.  
  13. virtual void aubiner() { // fermeture automatique (retrosignalisation)
  14. if (modePermanent) feux=S; else feux=C;
  15. manoeuvrer(); precedent()->maj();
  16. }
  17.  
  18. virtual void desaubiner() { // ouverture automatique (retrosignalisation)
  19. if (modePermanent) feux=calculFeux(); else return;
  20. manoeuvrer(); precedent()->maj();
  21. }
  22.  
  23. virtual typeFeux calculFeux() { // feux a l'ouverture
  24. if (cantonOccupe()) return S; // "canton" occupe ( precaution )
  25. if (suivant()->ferme()) return A; else return Vl;
  26. }
  27.  
  28. virtual void majFeux() { // appelé par le signal suivant
  29. if (ferme()) return;
  30. feux=calculFeux();
  31. manoeuvrer();
  32. precedent()->maj();
  33. }
  34. };

Comme d’habitude il faudra écrire une classe, héritant de CarreBAL, pour chaque signal particulier.

Carre rappel de ralentissement

Pour finir en beauté, un carré avec BAL et rappel de ralentissement, qui, bien évidemment, hérite de CarreBAL :

  1. class CarreBALRappelRalentissement: CarreBAL {
  2. CarreBALRappelRalentissement(int n):CarreBAL(n) {} // constructeur mini
  3. CarreBALRappelRalentissement(char* nm,int t,int n):CarreBAL(nm,t+BAL,n) {} // constructeur maxi
  4.  
  5. virtual typeFeux calculFeux() { // feux a l'ouverture
  6. if (ralentissement60()) if (suivant()->ferme()) return A+RRc; else return RRc; else
  7. if (ralentissement30()) if (suivant()->ferme()) return A+RR; else return RR;
  8. else if (suivant()s->ferme()) return A; else return Vl;
  9. }
  10. };

Seule la méthode calculFeux() doit être redéfinie. Un exemple complexe de signal particulier, pour bien montrer toutes les possibilités :

  1. class C6:CarreBALRappelRalentissement {
  2. C6(int n):CarreBALRappelRalentissement(n) {} // constructeur mini
  3. C6(char* nm,int t,int n):CarreBALRappelRalentissement(nm,t,n) {} // constructeur maxi
  4.  
  5. virtual Signal* suivant() { return selonAiguille(aig2,c2,c3); } // selon aig2
  6. virtual Signal* precedent() { return c2; }
  7.  
  8. virtual boolean cantonOccupe() { // canton dynamique
  9. if (aig1->directe()) return occupee(z10);
  10. return occupees(z8,z9);
  11. }
  12.  
  13. virtual boolean ralentissement60() { return aig1->deviee(); } // si aig1 deviee
  14. virtual boolean ralentissement30() { return aig1->directe() && aig2->deviee(); } // si aig1 directe et aig2 deviee
  15. };

Exemple d’utilisation :

  1. SignalBAL* c6=new C6(5);

La méthode cantonOccupe() montre que l’on peut avoir des "cantons" dynamiques, les zones qui le composent dépendent de la position d’aiguilles. On peut aussi avoir un ralentissement à 30 et/ou à 60 suivant la position des aiguilles.

Polymorphisme

Il est intéressant ici de regarder, sur ce dernier exemple, comment fonctionnent les mécanismes objet. Regardons pour commencer les relations d’héritages :

- C6 hérite de CarreBALRappelRalentissement qui hérite de CarreBAL qui hérite de SignalBAL qui hérite de Signal

Ces relations d’héritage constituent une branche de l’arbre formé par toutes les relations d’héritage. Voici l’arbre des signaux de cet article :

Arbre d’héritage des signaux

Les informaticiens ont l’habitude de représenter leurs arbres à l’envers, la racine est en haut, les feuilles sont en bas, la branche qui nous intéresse est la plus à droite.

Polymorphisme d’affectation

Premier constat, lors de l’instanciation du signal, la variable c6 est déclarée de type pointeur de SignalBAL et on lui affecte une instance d’une classe de type pointeur de CarreBALRappelRalentissement. On devrait plutôt écrire, pour respecter les types :

  1. C6* c6=new C6(5);

Si, au niveau des déclarations c’est plutôt utilisé par "fainéantise", le mécanisme est essentiel dans les classes. Si on regarde bien les classes zones et signaux, dans les méthodes on a des paramètres ou des résultats de type Signal* et lors des appels de méthodes, dans les exemples, il n’y a jamais de pointeur de Signal (la classe n’est pas instanciable), mais ce sont toujours des pointeurs sur des classes héritant de Signal. C’est essentiel, parce que cela permet de faire des actions sur les signaux sans se préoccuper du type réel du signal, tous les signaux fonctionnent globalement de la même façon, c’est là toute la puissance de la programmation objet.

On peut affecter à un pointeur sur une classe de base, un pointeur sur une classe dérivée (par héritage).

Polymorphisme de méthode

Plus subtil, regardons ce qui se passe à l’exécution quand on ouvre le signal avec, par exemple, l’instruction :

  1. c6->ouvrir();

Le type de c6 est pointeur de C6, classe qui n’a pas de méthode ouvrir(), on regarde alors dans la classe dont elle hérite CarreBALRappelRalentissement qui n’a pas de méthode ouvrir() non plus, on regarde alors dans la classe dont elle hérite CarreBAL, qui elle a une méthode ouvrir(). C’est celle là qui est utilisée, cette méthode fait plusieurs choses :

  1. ouvrir() { feux=calculFeux(); precedent()->maj(); manoeuvrer(); }

Détaillons :

- Premièrement, quand la méthode calculFeux() est appelée, on est dans la méthode ouvrir() de CarreBAL, classe qui a une méthode calculFeux(), mais ce n’est pas celle là qui est utilisée car le type réel de la variable c6 est C6 qui n’a pas de méthode calculFeux(), mais la classe dont elle hérite CarreBALRappelRalentissement a une méthode calculFeux(), donc c’est celle qui est utilisée. C’est très subtil comme mécanisme, l’exécution de la méthode calculFeux() va utiliser les mêmes mécanismes et ainsi de suite (on ne détaillera pas plus, ce serait vite inextricable).

En résumé bien qu’étant dans la classe CarreBAL, on n’utilise pas sa méthode calculFeux() (qui ne gère pas le rappel de ralentissement) mais bien la méthode de calculFeux() de CarreBALRappelRalentissement (qui elle gère le rappel de ralentissement) et c’est bien cela que l’on veut.

- Deuxièmement, quand la méthode precedent() est appelée, elle existe dans C6, donc c’est celle là qui est utilisée.

- Troisièmement, quand la méthode majFeux() est appelée, comme elle n’existe pas dans C6, ni dans CarreBALRappelRalentissement, mais existe dans CarréBAL, donc c’est celle là qui est utilisée.

- Finalement elle appelle la méthode manoeuvrer() qui n’existe pas dans C6, ni dans CarreBALRappelRalentissement, ni dans CarreBAL, ni dans SignalBAL, mais qui existe dans Signal, donc c’est celle là qui est utilisée.

C’est inextricable, je me suis trompé plusieurs fois en écrivant l’article, et on n’a pas regardé, pour simplifier, ce qui se passait dans les méthodes appelées dans les méthodes.

En pratique on écrit en respectant les règles simples de la programmation objet et c’est C++ qui fait ce qu’il faut à l’exécution, et il ne se trompe pas, lui. Pour bénéficier de ce mécanisme il suffit de mettre le mot clé virtual devant la méthode.

Si la méthode est virtuelle alors le choix de la méthode est fait à l’exécution en fonction du type réel de l’objet, sinon le choix est fait à la compilation en fonction du type déclaré de l’objet.

Avec ce mécanisme, dit de liaison dynamique ou liaison à l’exécution, tout se passe comme si à l’exécution, on partait du type réel de l’objet sur lequel la méthode est appliquée, et on recherchait dans l’arbre d’héritage en partant du type réel de l’objet, en remontant la branche vers la racine, la première occurrence de la méthode : c’est cette méthode qui est utilisée. On est sûr de trouver une méthode, car sinon il y aurait une erreur de compilation (en pratique le C++ utilise des tables de méthodes virtuelles).

Ce deuxième mécanisme de la programmation objet, complémentaire du premier, bien que plus subtil que le premier, est tout aussi essentiel et très puissant.

Bilan

Ces classes signaux sont assez compliquées, mais ce n’est pas la faute de la programmation objet, mais celle de la réglementation de la signalisation SNCF. A contrario, la programmation objet permet de prendre en compte relativement facilement toutes les contraintes de la signalisation.

Bien évidemment d’autres signaux sont possibles, français comme étrangers. Si je connais bien les subtilités de la signalisation française, je ne connais pas les signalisations étrangères et j’aurais du mal à écrire les classes correspondantes.

Les classes signaux sont assez compliquées et elles interfèrent beaucoup entre elles, certaines ont été testées (avec le locodrome), mais toutes n’ont pas été testées, il peut dont rester des erreurs.

Avec les classes aiguilles et zones, vues dans l’article précédent, et les classes signaux de cet article on a tout ce qu’il faut pour mettre en oeuvre facilement des itinéraires. Ce sera le sujet du prochain article et un exemple complet de mise en oeuvre sera aussi présenté (avec le locodrome).

Comme dans l’article précédent on trouvera dans le fichier ci-dessous toutes les classes bien écrites en C++, d’autres classes de signaux et d’autres exemples. Les classes comportent aussi des méthodes optionnelles (test de clignotement des feux, cibles à présenter, …).

Pour les signaux il y a quatre fichiers :
- un fichier Signal.h qui contient la classe Signal
- un fichier Signaux.h qui contient des signaux sans BAL
- un fichier SignalBAL.h qui contient la classe SignalBAL
- un fichier SignauxBAL.h qui contient des signaux avec BAL

Dans le prochain article, je terminerai avec les classes itineraire et une mise en oeuvre dans l’exemple du Locodrome.

En attendant relisez bien les articles 1 et 2 et rejoignez nous sur le forum :
Modélisation logicielle d’un réseau - le système de Pierre59

6 Messages

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

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

Automatisation du pont FLEISCHMANN 6152 (HO) avec un ESP32 (1)

Identifier et localiser vos trains avec le RFID/NFC et un bus CAN.

Etude d’un passage à niveau multivoies

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

Block Automatique Lumineux avec la carte shield "Arduino 4 relays"

Réalisation d’un affichage de gare ARRIVEE DEPART

Ménage à trois (Ordinateur, Arduino, réseau)

Réalisation d’un va-et-vient automatique et réaliste

Souris et centrale sans fil

Communications entre JMRI et Arduino

Annonces en gare avec la RFID

Une croix de pharmacie animée avec Arduino UNO

Réalisation d’un wagon de mesure (distance et vitesse)

Une manette simple et autonome pour LaBox

Éclairer le réseau (1)

Éclairer le réseau (2)

Block Automatique Lumineux à 8 cantons analogiques

Un décodeur DCC pour les plaques tournantes Fleischmann et Roco

Éclairer le réseau (3)

Éclairer le réseau (4)

Éclairer le réseau (5)

JMRI pour Ma première centrale DCC

Rocrail pour Ma première centrale DCC

CDM-Rail pour Ma première centrale DCC (1)

CDM-Rail pour Ma première centrale DCC (2)

Banc de test pour les décodeurs DCC

Ma première manette pour les aiguillages DCC

Mon premier décodeur pour les aiguillages DCC

Boitier 3D pour la station DCC minimale

Va-et-vient pour deux trains

Affichage publicitaire avec Arduino (1)

Affichage publicitaire avec Arduino (2)

Fabrication d’un programmateur pour microcontrôleurs ATtiny

TCO Web interactif avec des ESP32 et des ESP8266 (2)

TCO Web interactif avec des ESP32 et des ESP8266 (3)

TCO Web interactif avec des ESP32 et des ESP8266 (4)

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 que c’est ? (1)

La PWM : Qu’est-ce que c’est ? (2)

La PWM : Qu’est ce que c’est ? (3)

La PWM : Qu’est ce que c’est ? (4)

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

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

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

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

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

Contrôleur à télécommande infrarouge pour centrale DCC++

Gestion d’une gare cachée (1)

Gestion d’une gare cachée (2)

Gestion d’une gare cachée (3)

La carte Satellite V1

La carte Satellite V1

La carte Satellite V1

La carte Satellite V1

La carte Satellite V1

Les derniers articles

TCO Web interactif avec des ESP32 et des ESP8266 (4)


utpeca

TCO Web interactif avec des ESP32 et des ESP8266 (3)


utpeca

TCO Web interactif avec des ESP32 et des ESP8266 (2)


utpeca

Fabrication d’un programmateur pour microcontrôleurs ATtiny


Christian, Dominique, Jean-Luc

Affichage publicitaire avec Arduino (2)


catplus, Christian

Affichage publicitaire avec Arduino (1)


catplus, Christian

Un décodeur DCC pour les plaques tournantes Fleischmann et Roco


msport

Banc de test pour les décodeurs DCC


msport

Va-et-vient pour deux trains


msport

Boitier 3D pour la station DCC minimale


msport

Les articles les plus lus

La PWM : Qu’est-ce que c’est ? (1)

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

Un chenillard de DEL

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

La rétro-signalisation sur Arduino

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

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

La PWM : Qu’est ce que c’est ? (3)

La PWM : Qu’est-ce que c’est ? (2)

TCO Web interactif avec des ESP32 et des ESP8266 (4)