LOCODUINO

Un gestionnaire en C++ pour votre réseau

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

Itineraires et exemple

.
Par : Pierre59

DIFFICULTÉ :

Avec les classes aiguille, zone et signaux, on dispose des outils de base pour faire des choses intéressantes, notamment des itinéraires. Ceci sous la forme de poste(s) d’aiguillages à itinéraires ou de gestionnaire de réseau à itinéraires. Un exemple complet de gestionnaire de réseau sera aussi présenté avec un réseau minimaliste, le Locodrome. Ce gestionnaire de réseau complet est destiné à fonctionner sur un Arduino Uno, il pourra être testé complètement par le biais d’un TCO virtuel avec circulation effective de trains (tout aussi virtuels). Ce TCO virtuel est un programme écrit en Processing qui communique avec le gestionnaire par une liaison USB/série.

Sur le réseau SNCF il y a de nombreux postes d’aiguillage, dans les gares, aux bifurcations, … . Ces postes communiquent entre eux pour faire circuler les trains en assurant la sécurité. A la SNCF il y a une grande variété de types de postes d’aiguillage, en voici quelque uns :

  • des postes électromécaniques à leviers individuels
  • des postes électromécaniques à itinéraires
  • des postes électriques à relais à itinéraires
  • des postes à commande informatique
  • des postes entièrement informatisés

La plupart des ces postes sont équipés de TCO (Tableau de Contrôle Optique), affichant le tracé des voies, l’état des zones, l’état des signaux, … .

Sur nos réseaux on peut avoir plusieurs postes d’aiguillage avec chacun son TCO, un peu comme à la SNCF, le gestionnaire de réseau pouvant être centralisé ou réparti, mais souvent aussi on a un "poste" unique avec affichage sur le TCO de tout le tracé du réseau, c’est le gestionnaire de réseaux centralisé, c’est moins réaliste mais plus agréable à utiliser.

A l’usage les postes à itinéraires sont plus pratiques à exploiter sur nos réseaux, l’accent sera donc mis ici sur ce type de poste, donc sur un gestionnaire avec itinéraires.

Un itinéraire c’est un tracé de voie pour qu’un train puisse aller d’un point du réseau à un autre. Pour qu’un train puisse parcourir un itinéraire en toute sécurité, il faut positionner des aiguilles et ouvrir des signaux, mais aussi contrôler les enclenchements.

On distingue deux types de fonctionnement dans les itinéraires, le transit rigide et le transit souple. Avec le transit rigide, les ressources de l’itinéraire (aiguilles, zones, …) ne sont libérées que quand le train a parcouru tout l’itinéraire (à la libération de la dernière zone). Le transit souple libère les ressources au fur et a mesure du passage du train, les ressources libérées peuvent êtres réutilisées tout de suite par un autre itinéraire en attente.

Enclenchement

Une notion importante avec les postes d’aiguillage est l’enclenchement, il y a plusieurs types d’enclenchements :

  • des enclenchements entre itinéraires, des itinéraires qui utilisent des parties de voies communes ne peuvent bien évidemment pas êtres formés en même temps
  • des enclenchements avec les aiguilles, il faut que les aiguilles utilisées par l’itinéraire soient dans le bon sens
  • des enclenchements avec les signaux, on ne peut ouvrir le signal que si les aiguilles sont dans la bonne position
  • des enclenchements avec les zones, l’occupation d’une zone, consécutive à la présence d’un train, empêche la manoeuvre des aiguilles ou la formation d’itinéraires
  • des enclenchements dits d’approche, quand un itinéraire est formé et le signal ouvert (donc l’avertissement aussi), il n’est plus question de pouvoir fermer le signal dès que le train franchit le signal d’avertissement (si le train a vu l’avertissement ouvert et l’a dépassé c’est trop tard pour fermer le carré qui le suit).
  • des enclenchements par autorisations, quand plusieurs postes d’aiguillage peuvent établir des itinéraires utilisant des parties de voies communes (essentiellement des voies uniques) on utilise des autorisations, seul celui qui a l’autorisation peut établir l’itinéraire.

Ces enclenchements sont essentiels pour la sécurité des trains, pour éviter les prises en écharpe, les tamponnements frontaux (nez à nez), les déraillements sur les aiguilles mal positionnées, … . La prévention du rattrapage des trains reste réalisée par le "cantonnement" ou le "block" (BAL, BAPR, BMU, …).

Dans les postes d’aiguillages électromécaniques (à leviers individuels ou à leviers d’itinéraires) certains enclenchements sont réalisés par des moyens mécaniques (tables d’enclenchements ou serrures et clés). D’autres enclenchements sont réalisés par des moyens électriques. Dans les postes plus modernes tous les enclenchements sont réalisés par des moyens électriques, voire informatiques. Sur nos réseaux les enclenchements vont êtres réalisés par des moyens informatiques (gestionnaire de réseau) en collaboration avec des moyens électriques (rétrosignalisation).

Itinéraires

Les classes vues dans les articles précédents et la classe itinéraire que l’on va voir ici, ont tout ce qu’il faut pour réaliser tous ces enclenchements. Pour commencer on va s’intéresser aux itinéraires à transit rigide. Mais il ne faut pas oublier que les trains doivent respecter les signaux, condition indispensable pour avoir une sécurité totale.

Pour les itinéraires il faut une classe Itinéraire. Comme pour les zones on va avoir une classe de base Itinéraire et des classes dérivées pour chaque itinéraire particulier.

Un itinéraire peut avoir plusieurs états : libre, formé, en-attente, ...
Les états de base sont libre et formé, l’état en-attente est utilisé pour mémoriser des itinéraires qui ne sont pas formables mais qui seront formés dès que possible (mémorisation).

Pour réaliser les enclenchements et gérer les itinéraires on va utiliser essentiellement quatre méthodes :

  • une méthode formable() qui teste si l’itinéraire peut être formé (test des enclenchements entre itinéraires, avec les zones, avec les autorisations, …), en bref que rien ne s’oppose à la formation de l’itinéraire.
  • une méthode former() qui établit effectivement l’itinéraire sans contrôles (ils doivent être fait avant avec la méthode formable() ), il faut manoeuvrer les aiguilles et ouvrir les signaux, voire gérer les autorisations, et éventuellement le traçage l’itinéraire sur le TCO.
  • une méthode deformable() qui teste si l’itinéraire peut être libéré (pas de train sur l’itinéraire, pas d’enclenchement d’approche).
  • une méthode deformer() qui libère effectivement l’itinéraire sans contrôles (ils doivent être fait avant avec la méthode deformable() ), cela se limite généralement à des fermetures de signaux, voire à des libérations d’autorisations, et éventuellement effacement de l’itinéraire sur le TCO.
enum { ITINERAIRE_LIBRE,ITINERAIRE_FORME,ITINERAIRE_EN_ATTENTE, /* ... */ }; // etats possibles

class Itineraire { // classe de base des itineraires
  byte etat= ITINERAIRE_LIBRE; // l'etat de l'itineraire (libre, en attente, forme, ... )
  Itineraire() {} // constructeur vide

  boolean libre()     { return etat== ITINERAIRE_LIBRE; }
  boolean forme()     { return etat==ITINERAIRE_FORME; }
  boolean enAttente() { return etat==ITINERAIRE_EN_ATTENTE; } // OPTION

  virtual boolean formable() { return false; } // teste si l'itinéraire est formable
  virtual void former() {} // forme l'itineraire
   
  virtual boolean deformable() { return false; } // teste si l'itinéraire peut etre detruit
  virtual void deformer() {} // detruit l'itineraire

 boolean formation() { // essai de formation manuelle
  if (formable())   { former(); etat=ITINERAIRE_FORME; return true; } 
  return false; 
 } 
 boolean destruction() { // essai de destruction manuelle
  if (deformable()) { deformer(); etat=ITINERAIRE_LIBRE; return true; } 
  return false; 
 } 

 void detruire() { // destruction automatique (appelle par les methodes desactions() de zone)
  if (etat==ITINERAIRE_LIBRE) return;
  if (etat==ITINERAIRE_EN_ATTENTE) return;  // OPTION  
  deformer(); 
  tracer(false);  // OPTION         
  etat=ITINERAIRE_LIBRE; 
} 

 virtual void tracer(boolean b) {} // trace de l'tineraire   OPTION

 void effacer(); // effacement d'un itineraire memorise   OPTION
 };

On retrouve la variable d’état avec des méthodes de test et les quatre méthodes dont on a parlé avant. Ces méthodes seront redéfinies dans les classes dérivées.

La méthode formation() essaie de former l’itinéraire, elle teste si l’itinéraire est formable, si oui elle le forme, le résultat de la méthode rends compte de la formation effective ou non de l’itinéraire. Il est impératif que pendant l’exécution de cette méthode l’état global du réseau ne change pas. Dans le cas où l’itinéraire n’est pas formable on peut le mettre en mémoire (on en reparlera plus tard). Cette méthode est appelée par des commandes manuelles d’itinéraires.

La méthodedestruction() fait la même chose pour la destruction. Cette méthode est appelée par des commandes manuelles d’itinéraires.

La méthode détruire() est utilisée pour la destruction automatique des itinéraires, elle est appelée par la rétrosignalisation par le biais des méthodes desactions() de zone, aucun test préalable n’est nécessaire.

Les méthodes optionnelles tracer() et effacer() permettent de tracer et d’effacer un itinéraire sur un TCO.

Il y a aussi des méthodes utilitaires pour les enclenchements (voir plus loin et dans le fichier" Fonctions.h"). Pour des postes d’aiguillage avec beaucoup d’itinéraires le contrôle des enclenchements peut être assez lourd et compliqué, il ne faut pas hésiter à rajouter toutes les méthodes utilitaires nécessaires pour faciliter la prise en compte.

C’est tout, mais il reste pas mal à faire pour l’utilisation effective.

Il faut écrire une classe par itinéraire, cet classe héritant de la classe de base Itinéraire, exemple :

class TC: Itineraire { // un itineraire particulier de T vers C

  virtual boolean formable() { return libres(iTC,iCT) && // test d'enclenchement sur les itineraires
                                      libres(z8,z9); }  // test d'enclenchement sur les zones

  virtual void former() { aig1->devier(); aig2->directer(); // manoeuvre des aiguilles
                          c2->ouvrir(); }                 // ouverture du signal

  virtual boolean deformable() { return libres(z8,z9,z10); }  // test d'enclenchement sur les zones (dont zones d'approche)

  virtual void deformer() { c2->fermer(); } // fermeture du signal

  virtual void tracer(boolean b) { z8->tracer(b); z9->tracer(b); } // OPTION
};

Une méthode tracer() est rajoutée à la classe Zone pour mettre en oeuvre le tracé de l’itinéraire sur une zone particulière.

Autorisations

Avec la classe Itinéraire il peut être difficile de réaliser les enclenchements de nez à nez dans des cas un peu compliqués. Pour faciliter leur prise en compte on va introduire un outil annexe, l’autorisation.

Quand deux postes d’aiguillages sont reliés par une voie unique, il faut faire en sorte que les postes ne puissent envoyer deux trains sur la voie en même temps, cela ferait une catastrophe (nez à nez). A la SNCF pour se prémunir de telles situations on utilise un enclenchement appelé autorisation, le poste qui détient l’autorisation peut envoyer un train, l’autre pas.

Sur nos réseaux qui comportent souvent un ovale en voie unique, c’est souvent le même poste à chaque bout de la voie unique, mais on utilisera aussi une autorisation pour simplifier.

Si la voie unique est "cantonnée" on peut envoyer plusieurs trains, dans le même sens, à la suite (fonction du nombre de "cantons"), cela complique un peu la gestion de l’autorisation.

Une autorisation est prise pour un sens de circulation donné (pair ou impair), pour établir un itinéraire. Une fois l’autorisation prise elle doit être verrouillée jusqu’à la destruction de l’itinéraire, elle peut être alors rendue.

Dans le cas des voies doubles (sans IPCS -Installations Permanentes de Contre-Sens- et sans banalisation), on n’a pas besoin d’autorisation car il n’y a pas de risque de nez à nez.

Comme d’habitude on écrit une classe Autorisation. Cette classe comporte deux booléens un pour le sens et un pour le verrouillage. Il faut une méthode booléenne testant si l’autorisation est prenable, un méthode pour prendre l’autorisation et une méthode pour rendre l’autorisation :

class Autorisation { // classe de base des autorisations
  boolean sens=false; // pair=false   impair=true
  boolean prise=false; // verrou

  Autorisation() {} // constructeur vide

  boolean prenable(boolean s) {  // s : sens pair ou impair
    if (sens==s) return !prise;    // meme sens
    return !prise && aucunTrain(); // pas meme sens
  }

  void prendre(boolean s) { sens=s; prise=true; } // s : sens pair ou impair
  void rendre() { prise=false; }

  virtual boolean aucunTrain() { return false; }
};

Une autorisation est prenable pour un sens donné si elle est déjà dans le bon sens et pas prise (pas verrouillée) sinon il faut qu’il n’y ait aucun train (sur la voie unique) pour pouvoir changer le sens et qu’elle ne soit pas prise (pas verrouillée).

Les méthodes prendre() et rendre() gèrent les deux booléens.

On a aussi besoin d’une méthode virtuelle aucunTrain() qui teste s’il n’y a aucun train sur la voie unique.

Pour utiliser une autorisation, il faut écrire une classe qui hérite de la classe autorisation et qui redéfinit la méthode aucunTrain(), exemple :

class Aut:Autorisation {
  virtual boolean aucunTrain() { return libres(z1,z3,z4); }
};

Fonctions utilitaires

Quand on écrit un gestionnaire, on est très souvent amené à écrire des séries d’appels à une même méthode du genre :

objet1->methode(); objet2->methode(); objet3->methode(); …

Dans de tels cas il est pratique de pouvoir écrire :

methode(objet1,objet2,objet3, …);

Cela nécessite alors d’écrire des fonctions utilitaires avec un nombre variable d’arguments.

En C++ on peut avoir des fonctions avec un nombre variable d’arguments, mais ce n’est pas très pratique, il faut un premier argument pour préciser le nombre et il n’y a pas de contrôle ni de nombre ni de type des arguments (cela utilise des macros).

Aussi il est plus pratique d’avoir une fonction utilitaire pour chaque nombre d’arguments (on peut en rajouter si ce n’est pas suffisant). Ce mécanisme a déja été utilisé dans les articles précédents. Toutes ces fonctions utilitaires sont regroupées dans un fichier Fonctions.h.

Le Locodrome est un réseau minimaliste, qui convient parfaitement ici comme exemple de gestionnaire. Ce réseau comporte juste un ovale avec une voie d’évitement :

On trouvera aussi ce dessin dans les fichiers joints pour faciliter la lecture de l’article. On a :

  • deux aiguilles a0 et a1
  • six zones z0 à z5 dont deux "cantons" de BAL (z3 et z4)
  • six carrés C1 à C6
  • deux sémaphores de BAL S1 et S2
  • huit itinéraires XA, XB, AX, BX, YA, YB, AY et BY

il faudra aussi une autorisation pour la gestion des enclenchements de la voie unique (X à Y et inversement).

Les séparations des zones sont matérialisées part des petites croix (x).

On va écrire le gestionnaire le plus simple possible (destiné à tenir dans un Arduino Uno), pour cela on choisit d’utiliser des carrés sans BAL et juste deux sémaphores de BAL, ainsi qu’un transit rigide (le transit souple n’apporte ici pas beaucoup plus de possibilités). On pourrait simplifier encore en utilisant deux signaux d’avertissement à la place des sémaphores, mais cela limite encore plus les possibilités de circulation, et montre moins le potentiel du gestionnaire.

Pour rendre le gestionnaire fonctionnel, il sera interfacé avec un TCO virtuel en Processing, communiquant avec le gestionnaire par un "bus" USB/série. On pourra aussi y faire circuler des trains virtuels en conduite manuelle. Ce TCO sera décrit sur le forum.

Les possibilités de circulation sont limitées, on ne peut avoir raisonnablement que deux trains.

Conformément à la SNCF :

  • C3,C4,C5 et C6 sont des carrés avec avertissement, feux : C, A ou Vl. L’avertissement est pour les sémaphores suivants (S1 ou S2).
  • C1 et C2 sont des carrés avec rappel de ralentissement et avertissement, feux : C, A, Vl et RR. L’avertissement est pour les carrés suivants (C3 ou C5 et C4 ou C6). Le rappel de ralentissement est présenté quand l’aiguille protégée est déviée.
  • S1 et S2 sont des sémaphores de BAL avec ralentissement, feux : S, A, Vl et R. L’avertissement est pour les carrés suivants (C1 ou C2). Le ralentissement est présenté quand l’aiguille protégée par le carré suivant est déviée.

On va donc devoir écrire huit classes pour les signaux, six classes pour les zones, huit classes pour les itinéraires et une classe pour l’autorisation. Il faut aussi déclarer des variables pour les objets : deux aiguilles, huit signaux, six zones, huit itinéraires et une autorisation.

Objets

Il faut déclarer tous les objets dont on a besoin. Commençons par les objets aiguille :

Aiguille* a0=new Aiguille(0);  
Aiguille* a1=new Aiguille(1);

on utilise le plus petit constructeur, juste un n° pour commander effectivement les aiguilles.

Les signaux :

SignalBAL* s1=new S1(0); // la classe S1 hérite de SemaphoreBAL
SignalBAL* s2=new S2(1); // la classe S2 hérite de SemaphoreBAL

Signal* c1=new C1(2);  // la classe C1 hérite de CarreAvertissementRappelRalentissement
Signal* c2=new C2(3);  // la classe C2 hérite de CarreAvertissementRappelRalentissement

Signal* c3=new C3(4);  // la classe C3 hérite de CarreAvertissement
Signal* c5=new C5(5);  // la classe C4 hérite de CarreAvertissement
Signal* c4=new C4(6);  // la classe C5 hérite de CarreAvertissement
Signal* c6=new C6(7);  // la classe C6 hérite de CarreAvertissement

On utilise le plus petit constructeur, juste un n° pour commander effectivement les signaux.

Les zones :

Zone* z0=new Z0(0);            // Zone* z0=new Z0(0,c4,c3);    
Zone* z1=new Z1(1);            // Zone* z1=new Z1(1,c6,c5); 
Zone* z2=new Z2(2);            // Zone* z2=new Z2(2,NULL,NULL);
Zone* z3=new Z3(3);            // Zone* z3=new Z3(3,s2,c1);
Zone* z4=new Z4(4);            // Zone* z4=new Z4(4,c2,s2);
Zone* z5=new Z5(5);            // Zone* z5=new Z5(5,NULL,NULL);

On utilise le constructeur avec un n° pour commander le TCO. Les signaux attachés à la zone ne sont pas nécessaires pour le gestionnaire minimum, ils sont présents en commentaires pour montrer leur utilisation (ils pourront servir par exemple pour des extensions).

Les itinéraires :

Itineraire* xa=new XA(0);   
Itineraire* xb=new XB(1);
Itineraire* ax=new AX(2);
Itineraire* bx=new BX(3);
Itineraire* ya=new YA(4);
Itineraire* yb=new YB(5); 
Itineraire* ay=new AY(6); 
Itineraire* by=new BY(7);

On a besoin d’un n° d’itinéraire pour gérer le TCO. Une variable entière est ajoutée à la classe Itinéraire ainsi qu’un constructeur avec un paramètre entier.

Et finalement l’autorisation :

Autorisation* aut=new Aut;

On aura aussi besoin de quelques tables pour faire l’interface avec le TCO et pour la rétrosignalisation :

Signal* tableSignaux[] {s1,s2, c1,c2,c3,c4,c5,c6}; // la table des signaux pour le TCO

Zone* tableZones[] {z0, z1, z2, z3, z4, z5}; // la table des zones pour la retrosignalisation

Itineraire* tableItineraires[] {xa,xb,ax,bx,ya,yb,ay,by}; // la table des itinéraires pour TCO

Signaux

Il faut maintenant écrire les huit classes pour les signaux, comme le réseau est symétrique on va associer les classes semblables.

Classes S1 et S2

class S1:SemaphoreBALRalentissement {     

  S1(int n):SemaphoreBALRalentissement(n) {} // constructeur mini

  virtual Signal* suivant() { return c1; }
  virtual Signal* precedent() { return selonAiguille(a1,c4,c6); }
  virtual boolean cantonOccupe() { return z3->occupee(); }
};

Le suivant de s1 est le carré c1, le précédent est c4 si l’aiguille a1 est directe, c6 si elle dest déviée. Le "canton" est réduit à une seule zone z3. La classe S2 est symétrique de S1 :

class S2:SemaphoreBALRalentissement { 
public:
  S2(int n):SemaphoreBALRalentissement(n) {} // constructeur mini
  virtual Signal* suivant() { return c2; }
  virtual Signal* precedent() { return selonAiguille(a0,c3,c5); }
  virtual boolean cantonOccupe() { return z4->occupee(); }
};

Classes C1 et C2

class C1:CarreAvertissementRappelRalentissement {

  C1(int n):CarreAvertissementRappelRalentissement(n) {} // constructeur mini

  virtual Signal* suivant() { return selonAiguille(a0,c3,c5); } 
  virtual Signal* precedent() { return s1; }
  virtual boolean ralentissement30() { return a0->deviee(); }
};

Le signal précédent est s1, le signal suivant dépends de la position de l’aiguille a0. Si a0 est directe le suivant est c3, si elle est déviée c’est c5. Si l’aiguille a0 est déviée on a un ralentissement à 30.

La classe C2 est la symétrique de C1 :

class C2:CarreAvertissementRappelRalentissement {

  C2(int n):CarreAvertissementRappelRalentissement(n) {} // constructeur mini

  virtual Signal* suivant() { return selonAiguille(a1,c4,c6); }
  virtual Signal* precedent() { return s2; }
  virtual boolean ralentissement30() { return a1->deviee(); }
};

Classes C3 C5 C4 et C6

class C3:public CarreAvertissement {

  C3(int n):CarreAvertissement(n) {} // constructeur mini

  virtual Signal* suivant() { return s1; }
  virtual Signal* precedent() { return c1; }
};

Le signal suivant est s1, le précédent est c1.

Les trois autres classes sont semblables :

class C5:public CarreAvertissement {

  C5(int n):CarreAvertissement(n) {} // constructeur mini

  virtual Signal* suivant() { return s1; }
  virtual Signal* precedent() { return c1; }
};

class C4:public CarreAvertissement {

  C4(int n):CarreAvertissement(n) {} // constructeur mini

  virtual Signal* suivant() { return s2; }
  virtual Signal* precedent() { return c2; }
};

class C6:public CarreAvertissement {

  C6(int n):CarreAvertissement(n) {} // constructeur mini

  virtual Signal* suivant() { return s2; }
  virtual Signal* precedent() { return c2; }
};

Zones

Ici aussi on aussi a une symétrie, on va donc associer les classes semblables.

classes Z0 et Z1

class Z0 : public Zone {

  Z0(int n):Zone(n) {} // constructeur mini
  // Z0(int n,Signal* sp,Signal* si):Zone(n,sp,si) {} // constructeur

  // virtual Zone* suivantePaire() { return selonAiguille(a0,z2,NULL); }   
  // virtual Zone* suivanteImpaire() { return selonAiguille(a1,z5,NULL); }
};

Le constructeur n’a qu’un argument, le n° de la zone (pour le TCO). Le constructeur acceptant en plus deux signaux n’est pas nécessaire pour le Locodrome, il est ici en commentaire.

La zone paire suivante dépend de la position le l’aiguille a0. Si l’aiguille est directe c’est z2, si l’aiguille est déviée c’est NULL, il n’y a pas de zone suivante, si on fait passer un train il va dérailler. Cet aspect est essentiel pour le bon fonctionnement de la méthode provenance() de la classe Zone (cette méthode servira dans les extensions).

Même chose pour la suivante impaire. Pour la version minimale du Locodrome on en a pas besoin de ces méthodes, c’est pour cela qu’elles sont en commentaires, mais si on veut faire évoluer le Locodrome on en aura besoin.

La classe Z1 est très semblable (juste une inversion pour les aiguilles) :

class Z1 : public Zone { 

  Z1(int n):Zone(n) {} // constructeur mini
  // Z1(int n,Signal* sp,Signal* si):Zone(n,sp,si) {} // constructeur 

  // virtual Zone* suivantePaire() { return selonAiguille(a0,NULL,z2); }
  // virtual Zone* suivanteImpaire() { return selonAiguille(a1,NULL,z5); }
};

Classes Z2 et Z5

Les classes Z2 et Z5 sont concernées par la rétrosignalisation :

class Z2 : public Zone { 

  Z2(int n):Zone(n) {} // constructeur mini
  // Z2(int n,Signal* sp,Signal* si):Zone(n,sp,si) {} // constructeur

  virtual void actions() { aubiner(c1,c4,c6); }
  virtual void desactions() { detruire(xa,xb,ax,bx); }

  // virtual Zone* suivantePaire() { return z3; }
  // virtual Zone* suivanteImpaire() { return selonAiguille(a0,z0,z1); } 
};

Lors de l’occupation de la zone z2 la méthode actions() est appelée/ Il faut aubiner (fermer) un des signaux c1,c4 ou c6, un seul peut être ouvert. Comme on rechigne à tester lequel on les aubine tous. Pour ceux qui sont déjà fermés cela ne fera rien et celui qui est ouvert sera fermé. En pratique ou devrait écrire :

c1->aubiner(); c4->aubiner(); c6->aubiner();

Mais des fonctions utilitaires permettent de faciliter l’écriture (surtout sur de grands réseaux).

Lors de la libération de la zone z2 la méthode desactions() est appelée. Il faut détruire (on est en transit rigide) un des itinéraires xa, xb, ax ou bx/ Comme pour les signaux un seul peut être formé et ici aussi une fonction utilitaire est utilisée.

La classe Z5 est symétrique :

class Z5 : public Zone { 

  Z5(int n):Zone(n) {} // constructeur mini
  // Z5(int n,Signal* sp,Signal* si):Zone(n,sp,si) {} // constructeur

  virtual void actions() { aubiner(c2,c3,c5); }
  virtual void desactions() { detruire(ya,yb,ay,by); }

  // virtual Zone* suivantePaire() { return selonAiguille(a1,z0,z1); }
  // virtual Zone* suivanteImpaire() { return z4; }
};

Classes Z3 et Z4

Les deux zones z3 et z4 constituent les "cantons" pour les deux sémaphores de BAL s1 et s2, elles sont donc concernées aussi par la rétrosignalisation :

class Z3 : public Zone { 

  Z3(int n):Zone(n) {} // constructeur mini
  // Z3(int n,Signal* sp,Signal* si):Zone(n,sp,si) {} // constructeur

  virtual void actions() { s1->aubiner(); }  
  virtual void desactions() { s1->desaubiner(); }

  // virtual Zone* suivantePaire() { return z4; }
  // virtual Zone* suivanteImpaire() { return z2; }
};

Lors de l’occupation de la zone z3 il faut aubiner (fermer) le sémaphore s1. Lors de la libération de la zone z3 il faut désaubiner (ouvrir) le même sémaphore.

La classe Z4 est symétrique :

class Z4 : public Zone { 

  Z4(int n):Zone(n) {} // constructeur mini
  // Z4(int n,Signal* sp,Signal* si):Zone(n,sp,si) {} // constructeur

  virtual void actions() { s2->aubiner(); }
  virtual void desactions() { s2->desaubiner(); }

  // virtual Zone* suivantePaire() { return z5; }
  // virtual Zone* suivanteImpaire() { return z3; }
};

Il faut noter que si un train circule en sens pair (de X vers Y) le sémaphore de sens impair s1 va être aubiné (par occupation de z3), puis désaubiné (par libération de z3), c’est normal.

Ces classes zones montrent bien l’utilisation de la rétrosignalisation pour gérer les signaux et les itinéraires. Elles montrent aussi que les zones sont au coeur du gestionnaire de réseau, beaucoup de choses passent par elles (et ce n’est pas fini).

Itinéraires

Comme pour les zones on a une symétrie gauche/droite, on va donc aussi les regrouper.

Classes XA, XB, YA et YB
Commençons par l’itinéraire XA :

class XA:public Itineraire { 

  XA():Itineraire(0) {} // constructeur

  virtual boolean formable() { return libres(xa,xb,ax,bx, ya) && libres(z2,z0); }
  virtual void former() { a0->directer(); c1->ouvrir(); }
  virtual boolean deformable() { return libres(z3,z2); }   
  virtual void deformer() { c1->fermer(); }
  virtual void tracer(boolean b) { trace(b,z3,z2,z0); } // OPTION
};

Pour pouvoir former l’itinéraire XA il faut que tous les itinéraires utilisant la zone z2 soient libres soit XA,XB,AX et BX. Il faut aussi que l’itinéraire YA soit libre pour éviter les nez à nez. Il faut aussi que les zones z2 et z0 soient libres.

Pour former l’itinéraire il faut juste mettre l’aiguille a0 en position directe et ouvrir le carré c1.

Pour détruire l’itinéraire (manuellement) il faut que les zones utilisées par l’itinéraire soient libres, ici z2. Il faut aussi qu’il n’y ait pas d’enclenchement d’approche, donc que z3 soit libre. L’enclenchement d’approche devrait aussi inclure une partie de la zone z4 (juste devant le signal d’avertissement), mais c’est un peu plus difficile à mettre en oeuvre.

La destruction automatique des itinéraires est faite par la rétrosignalisation via les méthodesdesaction() des zones z2 et z5.

La méthode optionnelletracer() permet un tracé de l’itinéraire sur un TCO, elle liste les zones qui doivent êtres marquées (b=true) ou démarquées (b=false), en utilisant une fonction utilitaire.

Il faut remarquer que l’on ne peut envoyer un train sur la voie A que si celle-ci n’est pas occupée.
Envoyer un "train" sur une voie occupée est nécessaire, par exemple, pour mettre une locomotive en tête d’une rame, dans ce cas normalement on a un itinéraire en mode manoeuvre (avec le signal qui s’ouvre avec un feu manoeuvre (blanc lunaire)). Cet aspect n’est pas pris en compte ici, mais il est tout à fait réalisable.

L’itinéraire XB est très semblable :

class XB:public Itineraire { 
  XB():Itineraire(1) {} // constructeur
  virtual boolean formable() { return libres(xa,xb,ax,bx,yb) && libres(z2,z1); }
  virtual void former() { a0->devier(); c1->ouvrir(); }
  virtual boolean deformable() { return libres(z3,z2); }
  virtual void deformer() { c1->fermer(); }
   virtual void tracer(boolean b) { trace(b,z3,z2,z1); }
};

De même que les itinéraires YA et YB (symétriques) :

class YA:public Itineraire { 
  YA():Itineraire(4) {} // constructeur
  virtual boolean formable() { return libres(ya,yb,ay,by,xa) && libres(z5,z0); }
  virtual void former() { a1->directer(); c2->ouvrir(); }
  virtual boolean deformable() { return libres(z4,z5); }
  virtual void deformer() { c2->fermer(); }
  virtual void tracer(boolean b) { trace(b,z4,z5,z0); }
};

class YB:public Itineraire { 
  YB():Itineraire(5) {} // constructeur
  virtual boolean formable() { return libres(ya,yb,ay,by,xb) && libres(z5,z1); }
  virtual void former() {a1->devier(); c2->ouvrir(); }
  virtual boolean deformable()
  virtual void deformer() { libres(z4,z5); }
  /virtual void tracer(boolean b) { trace(b,z4,z5,z1); }
};

Classes AX BX et AY BY

Ces quatre itinéraires peuvent envoyer des trains sur la voie unique, comme cette voie unique comporte deux "cantons" la réalisation des enclenchements est plus délicate. Pour faciliter la chose on utilise une autorisation (aut).

Commençons par la classe AX :

class AX:public Itineraire {  // sens pair
  AX():Itineraire(2) {} // constructeur
  virtual boolean formable() { return libres(xa,xb,ax,bx,ay,by) && libres(z2,z3,z4) && aut->prenable(SENS_PAIR); }
  virtual void former() { a0->directer(); c4->ouvrir(); aut->prendre(SENS_PAIR); }
  virtual boolean deformable() { return libres(z0,z2); }     !
  virtual void deformer() { c4->fermer(); aut->rendre(); }
  virtual void tracer(boolean b) { trace(b,z0,z2,z3); }
};

Pour que l’itinéraire soit formable il faut pouvoir prendre l’autorisation. Quand on forme l’itinéraire il faut prendre l’autorisation et la rendre quand on le détruit.

Dans la méthode deformable() l’enclenchement d’approche devrait aussi porter sur les zones z5 et une partie de z4 (celle devant le signal d’avertissement), c’est c2 qui fait l’avertissement de c4, mais ce test doit être conditionnel, il dépend de la position de l’aiguille a1 et de l’ouverture du signal c2, pour simplifier on ne fera pas ce test.

il faut remarquer ici la complexité des enclenchements pour un si petit réseau (et on a simplifié les enclenchements d’approche), mais avec la programmation objet tout est réalisable (ou presque !).

La classe BX est très semblable :

class BX:public Itineraire { 
public:
  BX():Itineraire(3) {}
  virtual boolean formable() { return libres(xa,xb,ax,bx,ay,by) && libres(z2,z3,z4) && aut->prenable(SENS_PAIR); }
  virtual void former() { a0->devier(); c6->ouvrir();  aut->prendre(SENS_PAIR); }
  virtual boolean deformable() { return libres(z1,z2); }
  virtual void deformer() { c6->fermer(); aut->rendre(); }
  virtual void tracer(boolean b) { trace(b,z1,z2,z3); }
};

Les deux itinéraires de la partie droite :

class AY:public Itineraire { 
  AY():Itineraire(6) {} // constructeur
  virtual boolean formable() { return libres(ya,yb,ay,by,ax,bx) && libres(z5,z4,z3) && aut->prenable(SENS_IMPAIR); }
  virtual void former() { a1->directer(); c3->ouvrir(); aut->prendre(SENS_IMPAIR); }
  virtual boolean deformable() { return libres(z0,z5); }
  virtual void deformer() { c3->fermer(); aut->rendre(); }
   virtual void tracer(boolean b) { trace(b,z0,z5,z4); }
};

class BY:public Itineraire { 
  BY():Itineraire(7) {} // constructeur
  virtual boolean formable() { return libres(ya,yb,ay,by,ax,bx) && libres(z5,z4,z3) && aut->prenable(SENS_IMPAIR); }
  virtual void former() { a1->devier(); c5->ouvrir(); aut->prendre(SENS_IMPAIR); }
  virtual boolean deformable() { return libres(z1,z5); }
  virtual void deformer() {  c5->fermer(); aut->rendre(); }
  virtual void tracer(boolean b) { trace(b,z1,z5,z4); }
};

Mise en oeuvre

Voila, le programme de gestion du Locodrome est quasiment complet, il manque la réception des informations de rétrosignalisation et des commandes d’itinéraires ainsi que l’envoi des commandes au réseau (aiguilles, signaux, ... ). Pour pouvoir le tester (et plus généralement le mettre au point) en l’absence d’un vrai réseau, on va communicer avec le gestionnaire par le "bus USB/série".

Deux modes de fonctionnement sont possibles, soit en dialoguant directement avec le gestionnaire via le "Moniteur série", soit en utilisant un TCO virtuel (toujours avec le "bus USB/série"). Le mode de fonctionnement sera déterminé par la variable booléenne (TCO), les deux modes sont très proches.

Dans les fichiers joints, le gestionnaire à été modifié en ce sens. La réception d’information se fait dans la fonction loop(), chaque information se fait sous la forme d’UN caractère imprimable :

  • A à F pour occuper une zone, respectivement Z0 à Z5 (rétrosignalisation), fait l’appel de la méthode occuper() sur le bon objet zone.
  • a à f pour libérer une zone, respectivement Z0 à Z5 (rétrosignalisation), fait l’appel de la méthode liberer() sur le bon objet zone.
  • 0 à 7 pour commander un itinéraire, respectivement XA,XB,AX,BX, YA,YB,AY et BY, fait l’appel aux méthodes sur le bon objet itinéraire.

La fonction loop() teste aussi les itinéraires en attente pour essayer de les former. Il faut noter que toutes les informations reçues par le gestionnaire doivent êtres prises en compte uniquement dans la fonction loop(), si des informations sont issues d’interruptions il faut positionner une variable qui sera testée dans la fonction loop(), ceci pour garantir que quand une méthode du gestionnaire est appelée elle ira jusqu’au bout (sans que l’état du gestionnaire change) garantissant un état cohérent du gestionnaire.

Inversement le gestionnaire émet des commandes vers le réseau, ces commandes comportent trois parties, un code de commande, une information (couleur de signal ou de zone, position d’une aiguille, ... ) et un numéro d’objet.

C’est la fonction tco() (voir le fichier"TCO.h") qui réalise l’émission des commandes sur le "bus USB/série", plusieurs objets appellent cette fonction :

  • les aiguille pour affichage sur le TCO
  • les signaux (sémaphores ou carrés) pour affichage sur le TCO
  • les zones pour marquer l’état (libre ou occupé) sur le TCO et pour tracer les itinéraires sur le TCO
  • les itinéraires pour gérer les couleurs de boutons du TCO (avec ou sans clignotement)
  • ...

Quand le gestionnaire est en mode dialogue avec le "Moniteur série" (variable TCO=false) les commandes sont mises en forme pour les rendre lisibles dans le "Moniteur série". Quand le gestionnaire est en mode dialogue avec le TCO (variable TCO=true) les commandes sont codées sur deux octets (4 bits+4 bits+8 bits) qui seront décodés par le TCO virtuel.

Le "Moniteur série" permet des test basiques sur le gestionnaire puis
le TCO virtuel permet de tester de façon agréable et plus approfondie le gestionnaire, ceci avant de passer au réseau réel qui nécessitera aussi d’autres tests. Aperçu du TCO virtuel :

Le TCO virtuel permet aussi de faire circuler un ou deux trains tout aussi virtuels, donnant beaucoup de possibilités de test. Il faut remarquer que dans le gestionnaire rien n’est fait pour que les trains respectent les signaux, le gestionnaire est pour l’instant prévu pour une "marche à vue" des trains, on parlera de ce délicat problème dans un prochain article, par contre les train virtuels sont prévus pour respecter les signaux.

Bilan

Bien que minimaliste le Locodrome amène déjà à un gestionnaire assez compliqué, mais le programme tient facilement dans un Arduino uno en utilisant moins d’un millier d’octets de RAM et moins de 10 000 octets de mémoire FLASH, laissant de la place pour des extensions (arrêt des trains aux signaux par exemple).

La prise en compte de réseaux plus importants, n’est pas beaucoup plus compliquée, certes il y aura plus de classes à écrire et plus de contraintes sur les enclenchements, mais c’est tout à fait réalisable.

Comme dans les articles précédent on trouvera dans le fichier ci-dessous le programme de gestion du Locodrome bien écrit en C++, ainsi que le TCO virtuel écrit en Processing :

Dans les différents fichiers, du programme de gestion du Locodrome, tout ce qui est spécifique à l’interface avec le TCO virtuel est marqué "TCO". Cette version devient pour l’instant la version de référence, elle peut être utilisée pour d’autres réseaux en réécrivant le fichier "LocodromeBAL".

Pour essayer le programme de gestion du Locodrome avec le TCO virtuel consulter le fichier "Notice".

La discussion est ouverte sur le Forum : Modélisation logicielle d’un réseau - le système de Pierre59.

8 Messages

  • Un gestionnaire en C++ pour votre réseau (3) 8 octobre 2016 16:01, par PAT95

    Bonjour à tous
    Je suis régulièrement vos échanges sur le forum et c’est passionnant.
    J’ai testé Locodrome avec le TCO virtuel en Processing et c’est super ça fonctionne.
    Il me reste à décortiquer les programmes comme vous dites pour adapter tout ça à mon réseau. Y a du "boulot".
    J’attends l’édition du programme construction d’un TCO en processing cubes définitif.
    A bientôt.

    Répondre

    • Un gestionnaire en C++ pour votre réseau (3) 8 octobre 2016 17:33, par Pierre59

      Bonjour

      Pour adapter le gestionnaire de réseau du Locodrome à un autre réseau c’est pas difficile, c’est juste laborieux. Il faut essentiellement écrire toutes les classes zones, signaux et itinéraire adaptées au réseau, en s’aidant des exemples du Locodrome et en faisant beaucoup de copier/coller.

      Pour le TCO il faut mieux partir avec le programme du lien ci dessous, il n’est pas pollué par le déplacement des trains virtuels, il manque pas mal d’appareils de voie mais je peux les rajouter, je les utilise pour le TCO de mon réseau qui est bien plus compliqué que celui du Locodrome. La aussi c’est assez laborieux, mais on y arrive. Sinon il faut attendre le programme d’édition de TCO de Denis.
      Ne pas hésiter à poser des questions sur le forum.
      Pierre

      Voir en ligne : TCO en processing

      Répondre

  • Un gestionnaire en C++ pour votre réseau (3) 9 octobre 2016 11:36, par Dominique

    Je voudrais ajouter que j’ai mis en œuvre la méthode de Pierre dès son première article. C’est dans le forum, ici.

    J’y décris une méthode pas à pas dans laquelle on voit bien comment le réseau est modelisé par les méthodes propres des objets zones et aiguilles.

    Je dois dire aussi que je n’ai pas trouvé cela laborieux, mais, au contraire, lumineux.

    Répondre

  • Un gestionnaire en C++ pour votre réseau (3) 17 octobre 2016 10:57, par Jack56

    Bonjour,

    Bon boulot. Pour bien comprendre le fonctionnement je suis en train de décortiquer les différentes unités et j’ai un petit problème de compréhension avec la méthode suivante :

    void Signal::manoeuvrer() { // appele quand un signal change (feux et/ou cible)
      // COMPLETER manoeuvrer le signal (s'il existe )
      // gerer la zone d'arret si elle existe
      if (no >= 2) tco(CODE_CARRE,  coul(), no-1); // TCO
      else         tco(CODE_SIGNAL, coul(), no+1); // TCO
    }

    Est-ce bien no qu’il faut tester et pourquoi faire no-1 si no >= 2 et no+1 sinon ?
    J’avoue que pour le moment je n’ai pas regardé le TCO processing.

    Merci d’avance pour ta réponse.

    Répondre

    • Un gestionnaire en C++ pour votre réseau (3) 17 octobre 2016 11:33, par Pierre59

      Bonjour

      C’est juste un "bricolage" pour transformer les numéros des signaux du gestionnaire en numéros de signaux sur le TCO. Sur le TCO les sémaphores et les carrés sont numérotés séparément (1 à n).

      Pierre

      Répondre

  • Un gestionnaire en C++ pour votre réseau (3) 4 janvier 2017 11:44, par Christian

    Ce troisième article est pour moi la cerise sur le gâteau et je te remercie d’avoir livré un programme (Arduino + Processing) qui tourne sur un Uno car tout le monde n’a pas un Mega sous la main. Aussi, depuis, je m’amuse à faire tourner des trains virtuels sur un locodrome (virtuel lui aussi) mais avec report sur un TCO (non virtuel puisque dessiné sur mon écran d’ordinateur). J’invite donc tous ceux qui ont un Uno sous la main à tester ce programme : ils s’amuseront beaucoup !
    D’où ma question : tout cela a-t-il été testé sur un circuit réel et dans ce cas, comment relier l’Arduino au circuit ? A défaut, peut-on espérer qu’un schéma soit donné pour commander le locodrome (puisqu’on ne peut pas passer en revue tous les réseaux possibles) ? On peut penser que je suis un fainéant de ne pas adapter cela de moi-même, mais je me mets à la place du lecteur qui souhaiterait simplement recopier un cas concret (sans pour autant chercher à tout comprendre dans le détail) et donc je me fais l’avocat du diable.
    Est-ce qu’une vidéo ne serait pas souhaitable pour inciter les gens à regarder de plus près ces articles qui demandent un gros travail d’appropriation mais dont le résultat est à la hauteur. Encore bravo pour ce remarquable travail.

    Répondre

  • Un gestionnaire en C++ pour votre réseau (3) 4 janvier 2017 17:50, par Pierre59

    Bonjour

    Le Locodrome est juste un "démonstrateur" de ce qu’on peut faire avec le programme de gestion en POO proposé. Personnellement je ne peux pas aller plus loin car je n’ai pas de Locodrome, mais certains mettent en oeuvre le programme de gestion sur des cas plus réels (leurs réseaux) et ils en parleront sur le Forum. Il y aura aussi un quatrième article qui discutera un peu de la mise en oeuvre.

    Je pense que mieux qu’une vidéo il faut "jouer" avec les programmes, puisque c’est possible.

    Pierre

    Répondre

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

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 »

Les derniers articles

Les articles les plus lus