Auteur Sujet: Réseau Dominique  (Lu 248553 fois)

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3010
  • 100% Arduino et N
    • Voir le profil
Re : Réseau Dominique
« Réponse #120 le: janvier 25, 2023, 07:56:44 pm »
Pour compléter la batterie de capteurs nécessaires à la sécurité des trains, j'ai ajouté 4 détecteurs RFID/NFC et 8 points d'arrêt dans les 2 gares (2 pour chaque sens)

Les points d'arrêt sont décrits ici :
https://forum.locoduino.org/index.php?topic=290.msg12026#msg12026

Et les capteurs RFID ici :
https://www.locoduino.org/spip.php?article271

Pour les points d'arrêt, les messages CAN sont :
Point d’arrêt - satellite capteurs de positions et signaux carrés/sémaphore
- détections et libération d’un point d’arrêt : émis par le satellite
Id = 0x07 + N° de satellite = 0x09 et 0x0A (gare cachée) ou 0x07 et 0x08 (gare principale)
Les messages de détection ponctuelles sont envoyés vers le CENTRAL sur 1 octet de donnée avec l’Id :
-> data[0] :
bit 7 = 1 occ ou 0 libre;
bit 6 = sens Pair =1, ou sens Impair = 0;
bits 5..0 = numéro de zone
pas de répétition 500 ms

- Signaux :  reçu par le satellite
- 0x57 + Id sat = 0x57, 0x58, 0x59, 0x5A
data[0] :
bit 7 = signal 0 ou signal 1 et
bits 0..2 = signaux (0= VL =vert, 1 = A=jaune, 2 = S=rouge, >=3 = test = tous allumés)


Pour les capteurs RFID les messages CAN sont :
- RFID Capteurs : emis par le satellite RFID
- RFID OUEST : Id = 0x1A
messages 1 Can émis Id (court) = 0x1A (détecteur EST)
  octet 0 : bit 7 = 1, bit 6 = 0,            -> numéro de train = bits 0 à 5 (0 à 63) zone 25 (intérieur)
  octet 0 : but 7 = 0, bit 6 = 1             -> numéro de train = bits 0 à 5 (0 à 63) zone 16 (extérieur)
        octets 1..7 = nom du train

- RFID EST : Id = 0x1B
  messages 1 Can émis Id (court) = 0x1B (détecteur OUEST)
  octet 0 : bit 7 = 1, bit 6 = 0,            -> numéro de train = bits 0 à 5 (0 à 63) zone 30 (intérieur)
  octet 0 : bit 7 = 0, bit 6 = 1             -> numero de train = bits 0 à 5 (0 à 63) zone 11 (exterieur)
  octets 1..7 = nom du train

- ou message 2
  octet 0 : 0xC0 + index train créé ,  puis octets 1 à 7 = NUID

- ou message 3
  octet 0 : 0xFF              -> inconnu   (erreur, table pleine ou UID hors table)

- commandes RFID capteurs :
- commande 1 vers capteur RFID :  Id (long) = Id 0x1FFFFF60 (hex) + ID court (1A ou 1B) donc 0x1FFFFF7A (EST) ou 0x1FFFFF7B (OUEST)
  data[0] = 0x80 + index table des trains : demande numero train + NUID
  reponse = message 2
  - commande de changement de numéro de train
  data[0] = 0x10 demande de changement de numéro de train
  data[1] = prev numéro de train
  data[2] = nouveau numéro de train
pas de réponse mais sauvegarde eeprom
  - commande d’enregistrement EEPROM (après modifications)
data[0] = 0x20 sauvegarde eeprom
pas de réponse
« Modifié: janvier 25, 2023, 08:16:33 pm par Dominique »
Cordialement,
Dominique

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3010
  • 100% Arduino et N
    • Voir le profil
Re : Réseau Dominique
« Réponse #121 le: janvier 25, 2023, 08:24:03 pm »
Au total, un résumé des messages CAN utilisés dans mon réseau sont dans ce tableau :



On voit que le gestionnaire doit recevoir et traiter environ la moitié des messages qui circulent sur le bus et envoyer les autres 😅.

Ce qui donne un gros switch dans le programme :
//---------------  TRAITEMENT DES MESSAGES CAN  ----------------------//

void parseCANFrame(CAN_FRAME &frame) {
 
  switch (frame.id)  {

  //-------------- POINT D'ARRET EN GARE ---------------------//
  //--            pas de reponse à envoyer                  --//
 
  /*Detecteurs DAPD - data[0] :
    bit 7 = 1 occ ou 0 libre;
    bit 6 = coté Pair =1, ou coté Impair = 0; (il faudrait correspondre à : SENS_PAIR=0,SENS_IMPAIR=1)
    bits 5..0 = numéro de zone
    */
  //-------------------------------------------------// 
  case 0x07:        // detecteur DAPD gare principale sens impair : fin de z27 et z28 SENS IMPAIR avant z29
  case 0x08:        // detecteur DAPD gare principale sens pair: fin de z13 et z14 SENS PAIR avant z15
  case 0x09:        // detecteur DAPD gare cachée sens impair : fin de z22 et z23 SENS IMPAIR avant z24
  case 0x0A:        // detecteur DAPD gare cachée sens pair : fin de z4 et z5 SENS PAIR avant z6
  //-------------------------------------------------//
    //doPointDarret();
   
  break;

  //-------------------------------------------------//
  case 0x10:          // OCCUPATION ou LIBERATION de zone, venant de TCO
                      //  pas de réponse à envoyer
  //-------------------------------------------

  //doOccupationLiberation();
  break;    // sortie du case 0x10
       
   
  //-------------------------------------------------//
  case 0x11:          // demande clé aiguille venant de TCO
  //-------------------------------------------------//

    //doDemandeAiguille();
break;
   
  //-------------------------------------------------//
  case 0x12:          // réponse etat d'une aiguille, venant du controleur d'aiguilles
  //-------------------------------------------------//

    //doReponseAiguille();
    break;
   
  //-------------------------------------------------//
  case 0x13:          // message traction = curseur vitesse et direction d'une manette venant de la Traction
  //-------------------------------------------------//

    //doTraction();
    break;
   
  //-------------------------------------------------//
  case 0x14:          // lumiere d'une manette
  //-------------------------------------------------//

    //doLumiere();
    break;
   
  //-------------------------------------------------//
  case 0x15:          // DCC enabled de la traction
  //-------------------------------------------------//

    //doDCC();
    break;
   
  //-------------------------------------------------//
  case 0x16:         // intensité mA du booster DCC et alarme court-circuit éventuelle
  //-------------------------------------------------//
 
    //doCourant();
    break;
   
  //-------------------------------------------------//
  case 0x18:          // Mesure de vitesse d’un train
  //-------------------------------------------------//

    //doMesureVitesse();
    break;
   
  //-------------------------------------------------//
  case 0x19:          // confirmation d’enregistrement des états d’aiguilles en EEPROM
  //-------------------------------------------------//

    //doEnregistre();
    break;

  //-------------------------------------------------//
  case 0x1A:          // RFID EST gare cachée et gare principale
  case 0x1B:          // RFID OUEST gare principale
  case 0x1C:          // RFID ouest gare cachee - a faire
  //-------------------------------------------------//

    //doRFID();
    break;

  //-------------------------------------------------//
  case 0x1D:          // Evenement secteur (coupure d'alimentation)
  //-------------------------------------------------//

    //doSecteur();
    break;

  //-------------------------------------------------//
  case 0x1F:          // Alarme
  //-------------------------------------------------//

    //doAlarme();
    break;   

   //-------------------------------------------------//
  case 0x20:          // Reponse Aiguille -> TCO
  //-------------------------------------------------//

    //doReponseAiguille();
    break; 

  //-------------------------------------------------//
  default:
  //-------------------------------------------------//
    Serial.println();
    Serial.print(F("??? ID:"));
    Serial.print(frame.id, HEX);
    Serial.print(F(" Len: "));
    Serial.print(frame.length);
    Serial.print(F(" Data: 0x"));
    for (int count = 0; count < frame.length; count++) {
      Serial.print(frame.data.bytes[count], HEX);
      Serial.print(" ");
    }
  break;
       
  } // switch FrameId
} // fin parseCANFrame

Avant de rentrer dans les traitements du gestionnaire qui est presque totalement dédié au traitement des messages reçus, mais aussi à la présentation graphique du réseau et des évènements, il faut présenter de quoi est fait ce gestionnaire (pour le moment car il va évoluer au gré de mon temps disponible et des fonctions nouvelles que je vais ajouter).

« Modifié: janvier 26, 2023, 10:51:02 am par Dominique »
Cordialement,
Dominique

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3010
  • 100% Arduino et N
    • Voir le profil
Re : Réseau Dominique
« Réponse #122 le: février 18, 2023, 01:13:43 pm »
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.ino
C'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.h
C'est l'ensemble des constantes communes à tous les modules

Can.ino
C'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.h
C'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.h
Ces 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.h
C'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.h
C'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.h
Cet 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.h
Ce fichier contient les traitements des signaux, commandés à partir des autres objets.

Tactile.h
Cette 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  :D

Vous me suivez ???

« Modifié: février 18, 2023, 10:59:56 pm par Dominique »
Cordialement,
Dominique

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 3010
  • 100% Arduino et N
    • Voir le profil
Re : Réseau Dominique
« Réponse #123 le: février 18, 2023, 05:18:34 pm »
A ce stade de la présentation du gestionnaire, je ne vois pas l'interêt de diffuser le logiciel qui est, comme vous l'avez compris, très adapté à mon réseau donc inutilisable sur un autre réseau.

Par ailleurs, mon chantier est en perpétuel travaux et évolue chaque fois que je le peux.
Par exemple j'hésite encore dans la mise en oeuvre de la conduite des trains : manuelle ou automatique ou les deux sans doute. Mais priorité au manuel de toute façon.
La signalisation n'est pas encore branchée, donc probablement de la mise au point à faire.
Et puis encore l'animation du décor synchronisée avec les trains (passages à niveau, annonces en gare, etc..)
Le scripting de scenarii de conduite a été testée avec support sur carte SD. Je le garde pour un hiver prochain.

Mais si certains souhaitent comprendre et s'inspirer de parties qu'ils souhaiteraient réutiliser, je peux entrer dans les détails de ces parties. Il faudra donc me contacter pour cela en posant des questions ou en m'envoyant des MPs.
Cordialement,
Dominique