LOCODUINO

Gestion d’une gare cachée

Gestion d’une gare cachée (2)

« Show me the data »

.
Par : Jean-Luc

DIFFICULTÉ :

Avant de se lancer à programmer, il faut déterminer la façon dont les objets du réseau sont représentés dans le programme. Une bonne représentation permettra d’écrire un programme clair et un programme clair a de bien meilleures chances de fonctionner du premier coup.

Cet article va mettre en œuvre des notions avancées de programmation objet. Je vous engage à lire les articles de Thierry à ce sujet :

Dans un premier temps nous allons nous occuper de faire fonctionner cette gare indépendamment des capteurs et des actionneurs. Nous ajouterons ces derniers plus tard. La première chose à faire est d’exploiter le graphe de la gare pour y trouver une voie. Cet article traite de ce problème d’exploitation du graphe. Ensuite, faire fonctionner la gare consistera à trouver une voie en gare libre pour y stocker un train, d’une part, et sélectionner un train de la gare pour le mettre en circulation d’autre part. Ce sont deux problèmes voisins qui seront traités dans le prochain article.

À la fin de « Gestion d’une gare cachée (1) », nous avons vu la représentation de notre gare sous forme de graphe.

PNG - 131.2 kio

Nous allons traduire cette représentation en objets.

Tout d’abord, il nous faut définir des classes. Un nœud du graphe est un élément de voie. Déclarons donc une classe pour représenter un élément de voie. Il n’y a pas besoin de grand chose, juste un identifiant permettant de nommer les éléments de voie afin de les retrouver dans le graphe ainsi qu’un constructeur pour initialiser cet identifiant avec l’argument qui lui est passé et une fonction membre permettant de lire cet identifiant. Et puis nous avons besoin d’une fonction membre permettant de créer un chemin entre l’entrée de la gare et une voie de la gare, appelons cette fonction membre creeCheminVers, et entre la sortie de gare et une voie de la gare. Cette fonction membre prend pour argument l’identifiant de la voie vers laquelle on souhaite établir un chemin. Si la destination est trouvée, elle retourne true sinon elle retourne false. Il s’agit d’une fonction membre virtuelle car elle sera redéfinie concrètement pour un aiguillage, pour les voies de gare et pour les voies d’entrée et de sortie. Il s’agit même d’une fonction virtuelle pure, ce qui est dénoté par le = 0 à la fin de la déclaration. Non seulement elle est virtuelle mais, comme elle ne peut rien faire avec un objet de la classe Voie qui n’est connecté à rien, elle n’existera concrètement que dans les classes dérivées de Voie. Cela fait de Voie une classe abstraite, c’est à dire qu’on ne peut pas créer (instancier) des objets de cette classe.

class Voie
{
  private:
    byte mIdentifiant;

  public:
    Voie(byte identifiant) { mIdentifiant = identifiant; }
    byte idVoie() { return mIdentifiant; }
    virtual bool creeCheminVers(byte identifiant) = 0;
};

Cette classe de base est maintenant employée pour définir nos trois autres classes qui vont donc en hériter.

Ces voies concrètes définissent concrètement la fonction membre creeCheminVers comme nous allons le voir. Elle définissent également des fonctions membres permettant de connecter les voies entre elles. On est ici proche de ce que nous réalisons en construisant un réseau, nous connectons des voies entre elles. Ici, comme nous traitons le cas spécifique d’une gare cachée avec un graphe dédié à cette réalisation, les connexions sont simples à traiter. Une voie d’entrée connait, est connectée à, une seule autre voie, une voie en gare n’en connait aucune et un aiguillage connait deux autres voies.

Les voies d’entrée et de sortie

Commençons par la classe des voies d’entrée et de sortie. Ces voies sont particulières et différentes des voies de gare car elles ne sont connectées qu’à une seule autre voie. Nous allons appeler cette classe VoieExtreme. Cette classe possède une donnée membre, mVoie, qui est un pointeur vers une autre Voie, la fonction membre connecte qui permet de réaliser la connexion à cette autre voie et creeCheminVers.

Notez qu’on ne préjuge pas du type de voie connecté puisque mVoie est un pointeur vers un objet de type Voie. De fait, toute voie dérivée de Voie peut être connectée, la différence viendra de la fonction membre creeCheminVers qui sera différente selon la voie effectivement connectée. Comme cette fonction membre est virtuelle, c’est le creeCheminVers de la voie effectivement connectée qui sera appelé même si le pointeur est un pointeur de Voie.

Pour une VoieExtreme, la fonction creeCheminVers va comparer l’identifiant qui lui est passé en argument à l’identifiant de la voie d’entrée ou de sortie. Si il sont identiques, cela signifie que l’on est arrivé à destination, sinon on demande à la voie à laquelle elle est connectée de créer le chemin. Notez que cela permet de chercher un chemin vers une voie d’entrée, ce qui n’est guère utile car c’est le point de départ de la recherche mais c’est une généralisation du principe. Notez également l’initialisation de mVoie à NULL. Elle va permettre de savoir si le graphe a été correctement initialisé ou si certaines voies ne sont connectées à aucune autre et d’éviter de suivre des chemins qui n’existent pas.

class VoieExtreme : public Voie
{
  private:
    Voie *mVoie;

  public:
    VoieExtreme(byte identifiant) : Voie(identifiant) { mVoie = NULL; }
    void connecte(Voie *voie) { mVoie = voie; }
    virtual bool creeCheminVers(byte identifiant);
};

bool VoieExtreme::creeCheminVers(byte identifiant)
{
  if (identifiant == idVoie()) {
    return true;
  }
  else {
    if (mVoie != NULL) {
      return mVoie->creeCheminVers(identifiant);
    }
    else {
      return false;
    }
  }
}

Les voies de gare

Les voies de gare n’étant connectées à aucunes autres, rappelez vous que le graphe est orienté, elles sont très simples à représenter. Le constructeur se contente d’appeler le constructeur de Voie et creeCheminVers se contente de retourner true ou false selon que la voie de gare est la destination souhaitée ou non.

class VoieGare : public Voie
{
  public:
    VoieGare(byte identifiant) : Voie(identifiant) {}
    virtual bool creeCheminVers(byte identifiant);
};

bool VoieGare::creeCheminVers(byte identifiant)
{
  if (identifiant == idVoie()) {
    return true;
  }
  else {
    return false;
  }
}

Les aiguillages

Les aiguillages sont un petit peu plus compliqués car ils sont connectés à 2 éléments de voie. Ils vont donc posséder deux données membres, mVoieDroite et mVoieDeviee, des pointeurs vers ces deux éléments de voie et deux fonctions membres, connecteDroite et connecteDeviee vont permettre de les connecter. creeCheminVers cherche côté droit et côté dévié. Normalement un seul chemin est valable, ou aucun. On cherche d’abord côté droit et si on ne trouve pas, on cherche côté dévié. On va en profiter pour afficher, via la ligne série, le chemin qui a été trouvé afin de tester le programme.

class Aiguillage : public Voie
{
  private:
    Voie *mVoieDroite;
    Voie *mVoieDeviee;
  
  public:
    Aiguillage(byte identifiant) : Voie(identifiant) { mVoieDroite = mVoieDeviee = NULL; }
    void connecteDroite(Voie *voie) { mVoieDroite = voie; }
    void connecteDeviee(Voie *voie) { mVoieDeviee = voie; }
    virtual bool creeCheminVers(byte identifiant);
};

bool Aiguillage::creeCheminVers(byte identifiant)
{
  if (identifiant == idVoie()) {
    return true;
  }
  else {
    if (mVoieDroite != NULL && mVoieDeviee != NULL) {
      /* Uniquement si correctement connecte     */
      /* Cherche la destination cote voie droite */
      if (mVoieDroite->creeCheminVers(identifiant)) {
        /* Destination trouvee cote droit */
        Serial.print(idVoie());
        Serial.println(": Droit");
        return true;
      }
      else {
        /* Cherche la destination cote voie deviee */
        if (mVoieDeviee->creeCheminVers(identifiant)) {
          /* destination trouvee cote devie */
          Serial.print(idVoie());
          Serial.println(": Devie");
          return true;
        }
        else {
          /* destination non trouvee */
          return false;
        }
      }
    }
  }
}

Mise en œuvre de ces objets

Nous avons notre base, construisons maintenant notre graphe. Si il apparaît que d’autre fonctions sont nécessaires nous les ajouterons par la suite.

Tout d’abord, il nous faut créer des identifiants pour nos éléments de voie. Le meilleur moyen de faire cela est de créer un enum. Voir à ce propos « Trois façons de déclarer des constantes ».

enum {
  VOIE_ENTREE,
  VOIE_SORTIE,
  AIG_ENTREE_0,
  AIG_ENTREE_1,
  AIG_ENTREE_2,
  AIG_SORTIE_0,
  AIG_SORTIE_1,
  AIG_SORTIE_2,
  VOIE_GARE_0,
  VOIE_GARE_1,
  VOIE_GARE_2,
  VOIE_GARE_3,
  AUCUNE_VOIE
};

Notez le dernier identifiant, AUCUNE_VOIE qui va nous servir plus tard. En effet, lors de la recherche d’une voie libre, si cette voie est trouvée nous retournerons son identifiant. Si aucune voie libre n’est trouvée, nous retournerons AUCUNE_VOIE.

Notez également que ces identifiants sont des nombres et que si on veut les afficher pour tester notre système, la lecture risque d’être difficile. Nous allons donc créer une fonction afficheVoie qui à partir de cet identifiant va nous afficher en clair de quel élément de voie il s’agit. Seuls les aiguillages nous intéressent à priori.

void afficheVoie(byte identifiant)
{
  switch (identifiant) {
    case AIG_ENTREE_0:
      Serial.print("Aiguillage E0"); break;
    case AIG_ENTREE_1:
      Serial.print("Aiguillage E1"); break;
    case AIG_ENTREE_2:
      Serial.print("Aiguillage E2"); break;
    case AIG_SORTIE_0:
      Serial.print("Aiguillage S0"); break;
    case AIG_SORTIE_1:
      Serial.print("Aiguillage S1"); break;
    case AIG_SORTIE_2:
      Serial.print("Aiguillage S2"); break;
    default:
      Serial.print("** INCONNU **"); break;
  }
}

Et par conséquent nous allons modifier la fonction creeCheminVers de la classe Aiguillage en remplaçant les Serial.print(idVoie()) par afficheVoie(idVoie()).

Nous allons ensuite créer nos objets.

VoieExtreme voieEntree(VOIE_ENTREE);
VoieExtreme voieSortie(VOIE_SORTIE);
Aiguillage aiguillageE0(AIG_ENTREE_0);
Aiguillage aiguillageE1(AIG_ENTREE_1);
Aiguillage aiguillageE2(AIG_ENTREE_2);
Aiguillage aiguillageS0(AIG_SORTIE_0);
Aiguillage aiguillageS1(AIG_SORTIE_1);
Aiguillage aiguillageS2(AIG_SORTIE_2);
VoieGare voieGare0(VOIE_GARE_0);
VoieGare voieGare1(VOIE_GARE_1);
VoieGare voieGare2(VOIE_GARE_2);
VoieGare voieGare3(VOIE_GARE_3);

Pour l’instant, ils ne sont pas connectés entre eux. Pour réaliser cette connexion, nous allons utiliser les fonctions connecte, connecteDroite et connecteDeviee dans setup.

  /* Connexion des elements de voie du grill d'entree */
  voieEntree.connecte(&aiguillageE0);
  aiguillageE0.connecteDroite(&voieGare0);
  aiguillageE0.connecteDeviee(&aiguillageE1);
  aiguillageE1.connecteDroite(&aiguillageE2);
  aiguillageE1.connecteDeviee(&voieGare1);
  aiguillageE2.connecteDroite(&voieGare3);
  aiguillageE2.connecteDeviee(&voieGare2);
  /* Connexion des elements de voie du grill de sortie */
  voieSortie.connecte(&aiguillageS0);
  aiguillageS0.connecteDroite(&voieGare3);
  aiguillageS0.connecteDeviee(&aiguillageS1);
  aiguillageS1.connecteDroite(&aiguillageS2);
  aiguillageS1.connecteDeviee(&voieGare2);
  aiguillageS2.connecteDroite(&voieGare0);
  aiguillageS2.connecteDeviee(&voieGare1);

  ...

Ajoutons maintenant dans setup le test des chemins vers toutes les voies de la gare que ce soit depuis l’entrée ou depuis la sortie.

  Serial.begin(9600);
  Serial.println("***** TEST");
  /* Test des chemins */
  Serial.println("===== Test de l'entree");
  Serial.println("Voie gare 0");
  voieEntree.creeCheminVers(VOIE_GARE_0);
  Serial.println("Voie gare 1");
  voieEntree.creeCheminVers(VOIE_GARE_1);
  Serial.println("Voie gare 2");
  voieEntree.creeCheminVers(VOIE_GARE_2);
  Serial.println("Voie gare 3");
  voieEntree.creeCheminVers(VOIE_GARE_3);
  
  Serial.println("===== Test de la sortie");
  Serial.println("Voie gare 0");
  voieSortie.creeCheminVers(VOIE_GARE_0);
  Serial.println("Voie gare 1");
  voieSortie.creeCheminVers(VOIE_GARE_1);
  Serial.println("Voie gare 2");
  voieSortie.creeCheminVers(VOIE_GARE_2);
  Serial.println("Voie gare 3");
  voieSortie.creeCheminVers(VOIE_GARE_3);

L’exécution conduit au résultat attendu comme vous pouvez le constater ci-dessous.

***** TEST
===== Test de l'entree
Voie gare 0
Aiguillage E0: Droit
Voie gare 1
Aiguillage E1: Devie
Aiguillage E0: Devie
Voie gare 2
Aiguillage E2: Devie
Aiguillage E1: Droit
Aiguillage E0: Devie
Voie gare 3
Aiguillage E2: Droit
Aiguillage E1: Droit
Aiguillage E0: Devie
===== Test de la sortie
Voie gare 0
Aiguillage S2: Droit
Aiguillage S1: Droit
Aiguillage S0: Devie
Voie gare 1
Aiguillage S2: Devie
Aiguillage S1: Droit
Aiguillage S0: Devie
Voie gare 2
Aiguillage S1: Devie
Aiguillage S0: Devie
Voie gare 3
Aiguillage S0: Droit

Comment cela fonctionne-il ?

Tous les chemins possibles sont explorés jusqu’à ce que la destination soit trouvée. Si nous prenons, par exemple, le chemin de la voieEntree vers la voieGare1, l’exécution du programme est la suivante :

  • voieEntree demande à aiguillageE0
    • aiguillageE0 demande à voieGare0 (droite) qui répond false
    • aiguillageE0 demande à aiguillageE1 (gauche)
      • aiguillageE1 demande à aiguillageE2 (droite)
        • aiguillageE2 demande à voieGare3 (droite) qui répond false
        • aiguillageE2 demande à voieGare2 (droite) qui répond false
      • aiguillageE1 demande à voieGare1 (gauche) qui répond true TROUVÉ !
    • TROUVÉ !
  • TROUVÉ !

Lors de l’exploration, la fonction membre creeCheminVers des aiguillages affiche la position que doit prendre l’aiguillage pour conduire à la voie recherchée, le résultat est l’affichage que l’on voit ci-dessus.

Sketch complet
Contient les classes et le test de la gare cachée

Nous avons maintenant notre base pour automatiser notre gare cachée. Il nous manque la recherche d’une voie libre, ce qui n’est pas très compliqué à faire, la recherche d’une voie occupée, même chose, et l’intégration avec les capteurs et les actionneurs. Nous verrons tout ceci dans les articles qui suivent.

2 Messages

  • Gestion d’une gare cachée (2) 16 mai 2016 12:18, par Alain LM

    Bonjour,

    Merci pour cet excellent article, qui permet de rafraîchir les esprits embrumés comme le mien qui n’ont pas pratiqué la programmation objet depuis la sortie d’école, il y a déjà quelques temps.

    Si j’ai bien compris, la fonction "creeCheminVers" s’appelle récursivement, ce qui permet de parcourir un arbre/réseau de dimension quelconque.

    J’entrevois que ce principe pourrait être utilisé pour la recherche d’itinéraires entre deux points quelconque d’un réseau, avec une généricité peut-être encore plus importante que ce qui est proposé dans le projet "SGDD : Système de Gestion DD" sur ce même forum.

    Voir en ligne : SGDD : Système de Gestion DD (2)

    Répondre

    • Gestion d’une gare cachée (2) 16 mai 2016 23:30, par Jean-Luc

      Bonsoir,

      tout d’abord merci beaucoup pour vos compliments. creeCheminVers s’appelle récursivement. Effectivement, le principe peut être généralisé et il l’a d’ailleurs été, il y a un fil de discussion ouvert à ce propos sur le forum : http://forum.locoduino.org/index.ph...

      C’est en cours de développement :-)

      Répondre

Réagissez à « Gestion d’une gare cachée (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 »

Les derniers articles

Les articles les plus lus