Une liste des fichiers constituant le programme Gestionnaire permet de se faire une idée sur ce qu'il contient :
- CAN_Gestionnaire_154DUE.ino
- Constantes.h
- Can.ino
- ClassesDeBase.h
- Composants.h
- Description.h
- Graphiques.h
- Methodes.h
- Signaux.h
- Tactile.h
CAN_Gestionnaire_154DUE.inoC'est le programme principal qui articule toutes les ressources situées dans les autres fichiers, la version (date et numéro), les appels de bibliothèques, les variables globales, les fonctions Setup et Loop.
Constantes.hC'est l'ensemble des constantes communes à tous les modules
Can.inoC'est l'ensemble des traitements des messages Can qui sont reçus par le gestionnaire (on en a parlé juste avant ce post), ainsi que les utilitaires de commande CAN des trains, accessoires et TCO.
ClassesDeBase.hC'est la définition des classes de base des objets manipulés dans ce programma. C'est complètement inspiré des articles de Pierre59 (
https://www.locoduino.org/spip.php?article154 et toute la série), avec de nombreuses additions propres à mon réseau.
On va y trouver les Aiguilles, les Zones, les Signaux, les Itinéraires ou enclenchements, les Trains. Environ 300 lignes.
Exemple :
class Zone {
protected:
boolean etat=ZONE_LIBRE; // libre (false =0) ou occupé (true =1) // l'entrée dans une zone occupée provoque un arrêt immediat
boolean r60 = false; // true = ralentir 60
boolean r30 = false; // true = ralentir 30
boolean wAig = false;
byte trace=0; // pour tco OPTION
public:
int no; // le numero de la zone pour commander l'electronique ( TCO, relais, ... )
char* nom; // le nom de la zone OPTION
byte type = LIBRE; // etat de la zone (LIBRE=0, RALENTI=1, OCCUPANT=2, ENTRANT=3)
int circule; // sens de circulation
Signal* signalPair; Signal* signalImpair; // les signaux eventuels de la zone OPTION
Train* train=NULL; // le train actuel dans la zone
boolean zar=true; // indicateur de presence de zone d'arret pour detecteur ponctuel optique
protected:
//Zone(int n) { no=n; } // constructeur mini
//Zone(char* nm,int t,int n) { nom=nm, type=t; no=n; } // constructeur maxi
Zone(int n,Signal* sp,Signal* si,int s); // constructeur utilisé
public:
boolean occupee() { return etat; } // méthode de test
boolean libre() { return !etat; } // méthode de test
boolean testr60() { return r60; }
boolean testr30() { return r30; }
boolean retwAig() { return wAig; } // test presence aiguille
void setwAig() { wAig = true; } // declare contenir une aiguille
void ralentir() { if (!r60 && !r30) r60 = true; if (r60 && !r30) r30 = true; }
void repartir() { if (r60) {r30 = false;} else {r60 = false;} }
void ral30() { r30 = true;}
void ral60() { r60 = true;}
virtual Zone* suivantePaire() { return NULL; } // la zone suivante paire (éventuellement vide) OPTION
virtual Zone* suivanteImpaire() { return NULL; } // la zone suivante impaire (éventuellement vide) OPTION
virtual void actions() {} // les actions spécifiques à faire en cas d'occupation
virtual void desactions() {} // les actions spécifiques à faire en cas de libération
void occuper(); // appelée par la rétrosignalisation lors de l'occupation d'une zone
void liberer(); // appelée par la rétrosignalisation lors de la liberation d'une zone
int longueur(); // pas utilisé pour l'instant
void init(boolean e, boolean r3, boolean r6) {
etat=e; r30=r3; r60=r6; type = 0; zar=false;
if (etat) {
type=2; // OCCUPANT
} else {
type=0; // LIBRE
}
}
Composants.hCes composants sont les objets réels hérités des classes de base, afin de leur ajouter les propriétés et les fonctions dont ils ont besoin pour décrire mon réseau. Cela occupe 800 lignes environ que je vais simplifier car je n'ai pas besoin de tous les objets (notamment les signaux dans les tunnels et la gare cachée ainsi qu'une grande partie des itinéraires).
Exemple :
// 2 liaison VV-Hor (pair/droite=3siPdroite, impair/gauche=1+aiguillePdevié=4)
class Z2 : public Zone {
public:
Z2(int n,Signal* sp,Signal* si,int s):Zone(n,sp,si,s) {circule=s;}// constructeur
virtual Zone* suivantePaire();
virtual Zone* suivanteImpaire();
//virtual void actions();
//virtual void desactions();
};
Description.hC'est là dedans que la topographie et les comportements du réseau se trouvent décrits sur 600 lignes : quelles zones sont reliées aux 3 cotés des aiguilles, dans quelles zones sont situées les aiguilles et les signaux, avec les propriétés très importantes : suivantePaire() et suivanteImpaire() comme pour les zones consécutives :
Zone* Z0::suivantePaire() { return z1; }
Zone* Z0::suivanteImpaire() { return z40; }
ou autour des aiguilles :
Zone* Z2::suivantePaire() { return z3; }
Zone* Z2::suivanteImpaire() { return selonAiguille(a15,z1,selonAiguille(a16,NULL,z4)); }
ou pour chaque signal :
Signal* C1::suivant() { return selonAiguille(a9,selonAiguille(a11,NULL,NULL), NULL); }
Signal* C1::precedent() { return selonAiguille(a1,selonAiguille(a0, crr2,NULL),NULL); }
our pour les itinéraires :
boolean X4a7::formable() { return libres(x4a7,x3a4,x4a3,x5a7) && libres(z6,z7,z39); }
void X4a7::former() { a16->directer(); a13->devier(); c7->ouvrir(); }
boolean X4a7::deformable() { return libres(z4,z6,z39); }
void X4a7::deformer() { c7->fermer(); }
void X4a7::tracer(boolean b) { trace(b,z4,z6,z39);z7->tracer(b); }
Evidemment il faut avoir préparé un dessin du réseau avec la représentation de tous les éléments comme celui-ci :
Graphiques.hC'est un gros morceau ! C'est la représentation graphique du réseau sur un écran graphite tactile :
La représentation des positions d'aiguilles doit correspondre aux leds vertes sur le TCO manuel (ce qui n'est pas le cas ici, mais bon...):
A l'image des objets réels ci-dessus, j'ai décrit les même objets en graphique, avec les classes de base, les classes réelles et lees propriétés particulières. Par exemple la zone avec ses fonctions de tracé "libre" ou "occupé" ou "itinéraire":
class GZone {
public:
boolean etat=false; // libre (false) ou occupé (true)
int no; // le numero de la zone pour commander l'electronique ( TCO, relais, ... )
int na=-1; // le numéro de l'aiguille dans la zone, sinon -1
int nb=-1; // le numéro de l'aiguille dans la zone, sinon -1
GZone(int n) { no=n; } // constructeur mini
GZone(int n,int a) { no=n; na=a; } // constructeur OPTION
GZone(int n,int a,int b) {no=n; na=a; nb=b;} // constructeur OPTION
void occuper() {etat=true;} // appelée par la rétrosignalisation lors de l'occupation d'une zone
void liberer() {etat=false;} // appelée par la rétrosignalisation lors de la liberation d'une zone
virtual void tracer(int c, boolean s); // TCO c= couleur K_ouvert ou K_iti ou Kocc selon etat, K_ferme selon !etat
};
ou l'exemple d'une aiguille :
class GA0: public GAiguille {
public:
GA0(int n,int z):GAiguille(n,z) { no=n; nz=z; } // constructeur utilisé
void tracer(int c);
};
void GA0::tracer(int c) {
if (etat) {//droit
myGLCD.setColor(K_ferme);
geo.drawArc(90, 358, 38, 0, 45, 5);
myGLCD.setColor(c);
drawbarreH(90, 320, 27);
}
else { // devié
myGLCD.setColor(K_ferme);
drawbarreH(90, 320, 27);
myGLCD.setColor(c);
geo.drawArc(90, 358, 38, 0, 45, 5);
}
}
GAiguille* Ga0 = new GA0(0,26); // a0 est dans la zone z26
Pour déterminer toutes les coordonnées graphiques indiquées dans le code, un dessin détaillé a été nécessaire :
On notera que la manipulation de tous les objets se fait à l'aide de tableaux de pointeurs construit à la création des objets, tout comme les objets réels.
Methodes.hCet onglet contient toutes les méthodes utilitaires des objets de base et réels.
Par exemple cette fonction "provenance" qui permet de savoir quelle est la zone située à l'arrière d'une zone selon le sens de déplacement, ce qui permettra de trouver le train qui s'y trouve afin d'assurer le suivi des trains au fur et à mesure de leur déplacement:
Zone* Zone::provenance() {
Zone* zp;
Zone* zi;
if (circule==SENS_IMPAIR_SEUL) return suivantePaire(); // optimisation (si type)
if (circule==SENS_PAIR_SEUL) return suivanteImpaire(); // optimisation (si type)
zp=suivantePaire(); if (zp!=NULL && zp->libre()) zp=NULL; // candidate si non nulle et occupee
zi=suivanteImpaire(); if (zi!=NULL && zi->libre()) zi=NULL; // candidate si non nulle et occupee
if ((zp!=NULL) && (zi==NULL)) return zp;
if ((zp==NULL) && (zi!=NULL)) return zi;
if ((zp==NULL) && (zi==NULL)) {erreur(202); return NULL;} // pas de provenance
if ((zp!=NULL) && (zi!=NULL)) { // recherche avec les trains sur zp et zi
Train* tp;
Train* ti;
tp=zp->train; // a condition que tp != NULL
ti=zi->train; // a condition que ti != NULL
if ((tp==NULL) || (ti==NULL)) {return NULL;} // pas de provenance
if (tp->sens==SENS_IMPAIR && ti->sens==SENS_IMPAIR) return zp;
if (tp->sens==SENS_PAIR && ti->sens==SENS_PAIR) return zi;
return(ambiguite()); // sinon deux candidates (ambiguite)
}
return NULL;
}
Le principe est donc qu’un train qui entre dans une zone (détecté par son capteur) se trouvait dans la zone de provenance qui contient d’ailleurs la queue du train. C’est là qu’il faut récupérer le train pour marquer la zone suivante.
Signaux.hCe fichier contient les traitements des signaux, commandés à partir des autres objets.
Tactile.hCette partie regroupe les actions utilisateur sur l'écran tactile. Pour le moment la bibliothèque textile "UTouch" étant très insuffisante voire problématique, je me suis limité à 2 rangées de 12 boutons qui me permettent de sélectionner des itinéraires, commander des trains et lancer des tests pour debugger le programme. Par exemple pour visualiser le tracé d'un itinéraire avec une couleur différence des états libre et occupé.
Fonctions de mise au point L'avantage de disposer du graphique dans le programme est la facilité de mise au point.
Bien entendu j'ai passé pas mal de temps à trouver les bonnes coordonnées graphiques de éléments d'objets constituant chaque objet pour qu'ils se raccordent parfaitement.
J'ai aussi utilisé intensivement le moniteur série pour afficher des informations sur le déroulement du programme ou sur ce qu'il a prévu de faire.
Par exemple, je peux demander à voir la suite de zones accessibles vers l'avant d'une zone de départ et aussi vers l'arrière, en fonction des positions des aiguilles qui sont sur son passage. Ces fonctions affichent en même temps l'état "libre" ou "occupé" ainsi que le numéro de trains occupant.
////////// TRACE AVANT ///////////////////////////////
void traceAvant(Zone* zd) { // zone depart
Zone* zt = NULL; // zone de travail
Zone* zno = zd; // zone courante iterative
Serial.println();
for (int i=0; i<47; i++) { // MaxZone = 47
Serial.print(zno->no);Serial.print(zno->occupee()?"O":"L");Serial.print(zno->type);Serial.print(':');
if (zno->occupee()) {Serial.print(zno->train->no);}
Serial.print('-');
zt = zno->circule? zno->suivanteImpaire() : zno->suivantePaire(); //Paire 0, Impaire 1
if ((zt==NULL)||(zt==zd)) {
break;
} else {
zno = zt;
}
}
}
////////// TRACE ARRIERE ///////////////////////////////
void traceArriere(Zone* zd) { // zone depart
Zone* zt = NULL; // zone de travail
Zone* zno = zd; // zone courante iterative
Serial.println();
for (int i=0; i<47; i++) { // MaxZone = 47
Serial.print(zno->no);Serial.print(zno->occupee()?"O":"L");Serial.print(zno->type);Serial.print(':');
if (zno->occupee()) {Serial.print(zno->train->no);}
Serial.print('-');
zt = zno->provenance();
if ((zt==NULL)||(zt==zd)) {
break;
} else {
zno = zt;
}
}
}
Mise au point (suite)Une grande partie de la mise au point est faite avec l'analyse des messages CAN dans l'onglet CAN.
Les traitements de ces messages sont ponctués de "Serial.print" pour suivre le décodage des événements, donc pour suivre les trains et contrôler le bon déroulement du programme.
Par exemple cet extrait montre la progression du train N° 0 (BB20158) qui parcoure successivement les zones 8, 9, 10, 11 (avec détection RFID dans cette z11), puis 12, 13, 15 et 16 avec détection de ralenti à 30km/h, et une autre détection RFID :
z8 LIBRE zp7 T0 OK VOIE LIBRE DEVANT
z9 LIBRE zp8 T0 OK VOIE LIBRE DEVANT
VLoco 0>20
z10 LIBRE zp9 T0 OK VOIE LIBRE DEVANT
z11 LIBRE zp10 T0 OK VOIE LIBRE DEVANT
OUEST-ext z11 T0 (BB20158) Train ok
z12 LIBRE zp11 T0 OK VOIE LIBRE DEVANT
z13 LIBRE zp12 T0 OK VOIE LIBRE DEVANT
z15 LIBRE zp13 T0 OK VOIE LIBRE DEVANT 0A30 0>30
z16 RALENTI zp15 T0 OK VOIE LIBRE DEVANT 0V34 0>34
EST-ext z16 T0 (BB20158) Train ok
z5 RALENTI zp17 train perdu
Train 0 detecte sur z5
Dans cette exemple le train 0 est perdu en arrivant sur z5 à cause d'une mauvaise détection sur z17. Une procédure de découverte est lancée et le train est retrouvé sur z5 (Magique ?).
C’est un peu plus compliqué à lire quand plusieurs trains circulent simultanément mais c’est intéressant de suivre ce qui se passe sur le réseau. Comme il peut y avoir de nombreuses anomalies, il faut donc développer du code pour les détecter et les corriger ! C’est la vie normale de tous les programmeurs et il faut aimer ça
Vous me suivez