Voir les contributions

Cette section vous permet de consulter les contributions (messages, sujets et fichiers joints) d'un utilisateur. Vous ne pourrez voir que les contributions des zones auxquelles vous avez accès.


Messages - Marc-Henri

Pages: [1] 2 3 ... 9
1
Vos projets / Re : BALDUINO WIFI
« le: juillet 03, 2019, 01:37:20 pm »
Bonjour à tous,

Une application du protocole UDP est la diffusion d'un même message à plusieurs destinations (broadcast). Cela peut être utile pour détecter dynamiquement les systèmes connectés au réseau. Ensuite, une fois les systèmes connus, on peut établir des connexions TCP avec ces derniers. Dans l'IT, on voit souvent des applications à l'écoute en TCP et en UDP sur le même numéro de port.

Bonne journée et meilleures salutations.

2
Merci beaucoup Pierre et Thierry pour vos réponses.

Bonne fin de semaine et meilleures salutations.

Marc-Henri

3
Bonjour à tous,

Mon projet de réseau hivernal utilise abondamment les machines d'états à raison d'une par classe (http://forum.locoduino.org/index.php?topic=763.0). Bien que mon programme soit opérationnel, je ne suis pas 100% satisfait de mon code.

Les machines d'états se répartissent entre la méthode loop et les méthodes publiques appelées par les autres objets. De plus, les instructions switch deviennent vite très longues et la lisibilité du code n'est pas optimale.

Idéalement, j'aimerais avoir une fonction pour chaque état. En C, on utiliserait un pointeur sur fonction dont la valeur définirait l'état courant. De multiples exemples sont disponibles sur le web, y-compris pour les AVR.

Deux approches me paraissent possibles:

Pointeur vers méthode
En C++, il serait intéressant d'avoir une méthode pour chaque état et un pointeur vers la méthode correspondant à l'état courant.
  • Avantage: les méthodes ont directement accès aux membres de la classe.
  • Inconvénient: il ne semble pas simple de définir des pointeurs sur des méthodes.

Patron de conception État
L'alternative est le patron de conception état. Chaque état est implanté par une classe dérivée d'une classe abstraite. Ensuite, plutôt qu'un pointeur sur une méthode, c'est un pointeur sur la classe abstraite qui est mis en oeuvre. Le changement d'état consiste à changer l'instance d'objet correspondant à l'état courant.
  • Avantage: meilleur découpage du programme.
  • Inconvénient: les classes dérivées définies pour chaque état n'ont pas accès aux membres de la classe qui implémente la machine d'états. Il peut en effet être nécessaire de partager des données entre les différents états. Encore peu expérimenté en C++, j'aimerais savoir si on peut utiliser la notion d'amitié (friend class) pour cela.

Qu'en pensent les experts en C++ de Locoduino ?

Meilleures salutations.

4
Classe Signal
Nous avons suffisamment d'éléments pour gérer les 2 signaux aux extrémités de la gare. J'ai implanté des signaux groupés CFF à 3 leds: rouge, vert et jaune, avec les significations suivantes:


Rouge: sortie interdite.


Vert: sortie autorisée sur voie 1


Vert et jaune (orange dans la réalité, mais je n'ai pas trouvé de led orange de cette taille): sortie autorisée sur voie 2.

#include "Aiguilles.h"
#include "Tache.h"
#include "Zone.h"


class Signal: public Tache
{
public:
        Signal (Aiguilles *pxAiguilles, uint8_t xAig, Zone *pxZone, uint8_t xPinR, uint8_t xPinO, uint8_t xPinV);

        void setup ();
        void loop ();

private:
        Aiguilles *pAiguilles;
        const uint8_t nAig;
        Zone *pZone;       
        const uint8_t pinR, pinO, pinV;
};

La classe comporte un pointeur sur l'objet qui gère les aiguilles ainsi qu'un pointeur sur la zone correspondante. Les 2 signaux sont définis comme suit dans le fichier .ino en passant les adresses de l'objet aiguilles et des objets zoneL1 et zoneL2 en plus du numéro de l'aiguille et des pins correspondant aux leds.

Signal sigA1 (&aiguilles, C_A1, &zoneL1, SIG_A1_ROUGE, SIG_A1_ORANGE, SIG_A1_VERT);
Signal sigA2 (&aiguilles, C_A2, &zoneL2, SIG_A2_ROUGE, SIG_A2_ORANGE, SIG_A2_VERT);

Le code de la classe est simple, une machine à états n'est pas nécessaire dans ce cas. Les cathodes des leds sont reliées à l'Arduino, donc la valeur LOW allume la led et HIGH l'éteint:

#include "Aiguilles.h"
#include "Hardware.h"
#include "Signal.h"
#include "Zone.h"


Signal::Signal (Aiguilles *pxAiguilles, uint8_t xAig, Zone *pxZone, uint8_t xPinR, uint8_t xPinO, uint8_t xPinV) :
        pAiguilles (pxAiguilles), nAig (xAig), pZone (pxZone), pinR (xPinR), pinO (xPinO), pinV (xPinV) {}

void Signal::setup ()
{
        pinMode (pinR, OUTPUT);
        pinMode (pinO, OUTPUT);       
        pinMode (pinV, OUTPUT);       
} // Signal::setup


void Signal::loop ()
{
        if (pZone->bOccupee())
        {
                digitalWrite (pinR, LOW);
                digitalWrite (pinO, HIGH);
                digitalWrite (pinV, HIGH);       
        }
        else
        {
                digitalWrite (pinR, HIGH);
                digitalWrite (pinV, LOW);

                if (pAiguilles->eEtatAig (nAig) == DEVIE)
                {
                        digitalWrite (pinO, HIGH);
                }
                else
                {
                        digitalWrite (pinO, LOW);
                } // if
        } // if
} // Signal::loop

L'état de la zone définit s'il faut allumer la led verte ou la rouge. La led jaune quant à elle dépend en plus de l'état de l'aiguille.

5
Classe Aiguilles
Les 5 aiguilles du réseau sont commandées en 2 groupes:
  • les aiguilles de la gare cachée.
  • les aiguilles de la gare.
Prenons l'exemple de la gare cachée, les 2 aiguilles ont chacune un relais pour le choix de la position: normal ou dévié. En série avec ces 2 relais, un 3ème relais prend en charge l'impulsion. En gare principale, c'est le même principe avec une aiguille de plus pour la voie de garage.

Le graphe d'états est:



#define NORMAL  HIGH
#define DEVIE   LOW

#define NB_AIG  5
#define C_A1    0
#define C_A2    1
#define C_A3    2
#define C_A4    3
#define C_A5    4

struct Taig
{
        uint8_t eEtat;
        uint8_t eEtatNouv;
        uint8_t nRelais;
        uint8_t nRelaisGroupe;
        uint8_t nMin, nMax;     // Aiguilles min et max du groupe.
};
       

class Aiguilles: public Tache
{
public:
        void setup ();
        void loop ();

        void changer (uint8_t nxAig, uint8_t exEtat);
        uint8_t eEtatAig (uint8_t nxAig);
        uint8_t bChangee (uint8_t nxAig);

private:
        struct Taig aig[NB_AIG];
        uint8_t nAigCourante, eEtatGlobal;
        uint32_t tDebut;
};

#include "Aiguilles.h"
#include "Hardware.h"


#define DUREE_ATTENTE_AIG   250
#define DUREE_ATTENTE_COM       150
#define DUREE_ACTIVE    70

#define E_INACTIF       0
#define E_ATTENTE       1
#define E_ATTENTE_COM   2
#define E_ACTIF         3


#ifdef DEBUG_AIG
static void tracerEtat (uint8_t exEtat)
{
        Serial.print (millis (), DEC);
        Serial.print (" - Aig: ");
        switch (exEtat)
        {
                case E_INACTIF: Serial.println ("E_INACTIF"); break;
                case E_ATTENTE: Serial.println ("E_ATTENTE"); break;
                case E_ATTENTE_COM: Serial.println ("E_ATTENTE_COM"); break;
                case E_ACTIF: Serial.println ("E_ACTIF"); break;
        } // switch
} // tracerEtat
#else
#define tracerEtat(x)
#endif

#ifdef DEBUG_AIG
static void afficherAig (uint8_t nxAig, uint8_t exEtat)
{
        Serial.print ("Aig: ");
        Serial.print (nxAig+1, DEC);
        Serial.print (" - ");
        if (exEtat == NORMAL)
        {
                Serial.println ("normal");
        }
        else
        {
                Serial.println ("dévié");
        } // if
} // afficherAig
#else
#define afficherAig(x, y)
#endif


void Aiguilles::setup ()
{
        #ifdef ACTIVER_AIG
        pinMode (REL_COM_GC, OUTPUT);
        pinMode (REL_A4, OUTPUT);
        pinMode (REL_A5, OUTPUT);

        pinMode (REL_COM_GARE, OUTPUT);
        pinMode (REL_A1, OUTPUT);
        pinMode (REL_A2, OUTPUT);
        pinMode (REL_A3, OUTPUT);
        #endif

        aig[C_A1].nRelais = REL_A1;
        aig[C_A2].nRelais = REL_A2;
        aig[C_A3].nRelais = REL_A3;
        aig[C_A4].nRelais = REL_A4;
        aig[C_A5].nRelais = REL_A5;

        aig[C_A1].nRelaisGroupe = REL_COM_GARE;
        aig[C_A2].nRelaisGroupe = REL_COM_GARE;
        aig[C_A3].nRelaisGroupe = REL_COM_GARE;       
        aig[C_A4].nRelaisGroupe = REL_COM_GC;
        aig[C_A5].nRelaisGroupe = REL_COM_GC;

        // Au démarrage, pour positionner toutes les aiguilles, on considère
        // que le nouvel état est systématiquement différent de l'état courant.
        aig[C_A1].eEtat = NORMAL;
        aig[C_A2].eEtat = DEVIE;
        aig[C_A3].eEtat = NORMAL;
        aig[C_A4].eEtat = DEVIE;
        aig[C_A5].eEtat = NORMAL;

        aig[C_A1].eEtatNouv = DEVIE;
        aig[C_A2].eEtatNouv = NORMAL;
        aig[C_A3].eEtatNouv = DEVIE;
        aig[C_A4].eEtatNouv = NORMAL;
        aig[C_A5].eEtatNouv = DEVIE;

        aig[C_A1].nMin = C_A1;
        aig[C_A2].nMin = C_A1;       
        aig[C_A3].nMin = C_A1;
        aig[C_A4].nMin = C_A4;               
        aig[C_A5].nMin = C_A4;

        aig[C_A1].nMax = C_A3;
        aig[C_A2].nMax = C_A3;       
        aig[C_A3].nMax = C_A3;
        aig[C_A4].nMax = C_A5;               
        aig[C_A5].nMax = C_A5;             
               
        nAigCourante = C_A1;
        eEtatGlobal = E_INACTIF;
        tracerEtat (eEtatGlobal);
} // Aiguilles::setup


void Aiguilles::loop ()
{
        switch (eEtatGlobal)
        {
                case E_INACTIF:
                        // Demande d'activation d'une aiguille.
                        if (aig[nAigCourante].eEtatNouv != aig[nAigCourante].eEtat)
                        {
                                eEtatGlobal = E_ATTENTE;
                                tracerEtat (eEtatGlobal);
                                tDebut = millis ();     
                               
                                #ifdef DEBUG_AIG
                                Serial.println ("************");
                                Serial.print ("Aig: ");
                                Serial.print (nAigCourante+1, DEC);
                                Serial.print (" - changement demandé - ");
                                if (aig[nAigCourante].eEtatNouv == NORMAL)
                                {
                                        Serial.println ("normal");
                                }
                                else
                                {
                                        Serial.println ("dévié");
                                } // if
                                #endif
                        }
                        else
                                nAigCourante = (nAigCourante + 1) % NB_AIG;
                break;
               
                case E_ATTENTE:
                        if ((millis () - tDebut) > DUREE_ATTENTE_AIG)
                        {
                                eEtatGlobal = E_ATTENTE_COM;
                                tracerEtat (eEtatGlobal);
                                tDebut = millis ();
                               
                                // On ne change que l'aiguille en question, les autres du même groupe gardent leurs anciennes valeurs.
                                for (uint8_t i = aig[nAigCourante].nMin; i <= aig[nAigCourante].nMax; i++)
                                {
                                        if (nAigCourante == i)
                                        {       
                                                #ifdef ACTIVER_AIG
                                                digitalWrite (aig[i].nRelais, aig[i].eEtatNouv);
                                                #endif

                                                afficherAig (i, aig[i].eEtatNouv);
                                        }
                                        else
                                        {
                                                #ifdef ACTIVER_AIG
                                                digitalWrite (aig[i].nRelais, aig[i].eEtat);
                                                #endif

                                                afficherAig (i, aig[i].eEtat);
                                        } // if
                                } // for
                        } // if
                break;

                case E_ATTENTE_COM:
                        if ((millis () - tDebut) > DUREE_ATTENTE_COM)
                        {
                                eEtatGlobal = E_ACTIF;
                                tracerEtat (eEtatGlobal);             
                                tDebut = millis ();
                               
                                #ifdef ACTIVER_AIG
                                digitalWrite (aig[nAigCourante].nRelaisGroupe, HIGH);
                                #endif

                                #ifdef DEBUG_AIG
                                if (aig[nAigCourante].nRelaisGroupe == REL_COM_GARE)
                                {
                                        Serial.println ("Aig: relais gare HIGH");
                                }
                                else
                                {
                                        Serial.println ("Aig: relais gare cachée HIGH");               
                                } // if
                                #endif       
                        } // if
                break;

                case E_ACTIF:
                        if ((millis () - tDebut) > DUREE_ACTIVE)
                        {
                                eEtatGlobal = E_INACTIF;             
                                tracerEtat (eEtatGlobal);
                                                               
                                // Une aiguille est en cours d'activation, on peut relacher le relais du groupe correspondant.
                                #ifdef ACTIVER_AIG
                                digitalWrite (aig[nAigCourante].nRelaisGroupe, LOW);
                                #endif

                                #ifdef DEBUG_AIG
                                if (aig[nAigCourante].nRelaisGroupe == REL_COM_GARE)
                                {
                                        Serial.println ("Aig: relais gare LOW");
                                }
                                else
                                {
                                        Serial.println ("Aig: relais gare cachée LOW");
                                } // if
                                #endif                               

                                // On peut désactiver tous les relais.
                                #ifdef DEBUG_AIG
                                Serial.println ("Aig: désactivation des relais");
                                #endif
                       
                                for (uint8_t i = C_A1; i <= C_A5; i++)
                                {
                                        #ifdef ACTIVER_AIG
                                        digitalWrite (aig[i].nRelais, DEVIE);
                                        #endif                               
                                } // for

                                // L'aiguille courante a été changée, passer à la suivante.
                                aig[nAigCourante].eEtat = aig[nAigCourante].eEtatNouv;
                                nAigCourante = (nAigCourante + 1) % NB_AIG;
                        } // if
                break;
        } // switch
} // Aiguilles::loop


void Aiguilles::changer (uint8_t nxAig, uint8_t exEtat)
{
        aig[nxAig].eEtatNouv = exEtat;

        #ifdef DEBUG_AIG_DEM
        Serial.print ("Aig: ");
        Serial.print (nxAig+1, DEC);
        Serial.print (" - méthode Changer appelée - ");
        if (exEtat == NORMAL)
        {
                Serial.println ("normal");
        }
        else
        {
                Serial.println ("dévié");       
        } // if
        #endif
} // Aiguilles::changer


uint8_t Aiguilles::eEtatAig (uint8_t nxAig)
{
        if (aig[nxAig].eEtatNouv != aig[nxAig].eEtat)
        {
                return (aig[nxAig].eEtatNouv);
        }
        else
        {
                return (aig[nxAig].eEtat);
        } // if
} // Aiguilles::eEtatAig


uint8_t Aiguilles::bChangee (uint8_t nxAig)
{
        return (aig[nxAig].eEtatNouv == aig[nxAig].eEtat);
} // Aiguilles::bChangee

Cette classe parcourt continuellement le tableau des 5 aiguilles et elle teste si une nouvelle position est demandée. Elle fait en sorte de n'activer qu'une aiguille à la fois. Les états permettent des attentes entre les activations des relais de position et d'impulsion.

6
Bonjour à tous,

Voici la suite du programme de contrôle de mon réseau. Avant de décrire les classes, commençons par le programme principal qui me permettra d'illustrer les principes mis en oeuvre.

//
//      Reseau_hivernal.ino
//
//      2019.02.15      Création.

#include <stdint.h>

#include "Affichage.h"
#include "Aiguilles.h"
#include "Boutons.h"
#include "ChefDeGare.h"
#include "GareCachee.h"
#include "Hardware.h"
#include "Itineraire.h"
#include "LED.h"
#include "Signal.h"
#include "Tache.h"
#include "Zone.h"


#define PER_CLIGN       250
LED led_board (LED_BOARD, PER_CLIGN);
Affichage affichage;
Boutons boutons;
Modes selMode;
Aiguilles aiguilles;


#ifdef DEBUG_ZONE
Zone zoneL1 (L1_DET, DELAI_ENTREE_LIGNE, DELAI_SORTIE_LIGNE, "Ligne 1");
Zone zoneL2 (L2_DET, DELAI_ENTREE_LIGNE, DELAI_SORTIE_LIGNE, "Ligne 2");
Zone zoneG1 (G1_DET, DELAI_ENTREE_GARE, DELAI_SORTIE_GARE, "Gare 1");
Zone zoneG2 (G2_DET, DELAI_ENTREE_GARE, DELAI_SORTIE_GARE, "Gare 2");
Zone zoneGC1G (GC1G_DET, 0, DELAI_SORTIE_GARE_CACHEE, "Gare cachée 1G");
Zone zoneGC1D (GC1D_DET, 0, DELAI_SORTIE_GARE_CACHEE, "Gare cachée 1D");
Zone zoneGC2G (GC2G_DET, 0, DELAI_SORTIE_GARE_CACHEE, "Gare cachée 2G");
Zone zoneGC2D (GC2D_DET, 0, DELAI_SORTIE_GARE_CACHEE, "Gare cachée 2D");       
#else
Zone zoneL1 (L1_DET, DELAI_ENTREE_LIGNE, DELAI_SORTIE_LIGNE);
Zone zoneL2 (L2_DET, DELAI_ENTREE_LIGNE, DELAI_SORTIE_LIGNE);
Zone zoneG1 (G1_DET, DELAI_ENTREE_GARE, DELAI_SORTIE_GARE);
Zone zoneG2 (G2_DET, DELAI_ENTREE_GARE, DELAI_SORTIE_GARE);
Zone zoneGC1G (GC1G_DET, 0, DELAI_SORTIE_GARE_CACHEE);
Zone zoneGC1D (GC1D_DET, 0, DELAI_SORTIE_GARE_CACHEE);
Zone zoneGC2G (GC2G_DET, 0, DELAI_SORTIE_GARE_CACHEE);
Zone zoneGC2D (GC2D_DET, 0, DELAI_SORTIE_GARE_CACHEE);       
#endif

VoieGareCachee voieGC1 (GC1_ABC, &zoneGC1G, &zoneGC1D);
VoieGareCachee voieGC2 (GC2_ABC, &zoneGC2G, &zoneGC2D);

GareCachee gareCachee (&aiguilles, &affichage, &voieGC1, &voieGC2, &zoneL1, &zoneL2);

Signal sigA1 (&aiguilles, C_A1, &zoneL1, SIG_A1_ROUGE, SIG_A1_ORANGE, SIG_A1_VERT);
Signal sigA2 (&aiguilles, C_A2, &zoneL2, SIG_A2_ROUGE, SIG_A2_ORANGE, SIG_A2_VERT);

VoieGare voieG1 (G1_ABC, &zoneG1);
VoieGare voieG2 (G2_ABC, &zoneG2);


#ifdef DEBUG_ITIN
ItinGareVersCachee it_G1_GC_Gauche (&zoneL2, &aiguilles, &gareCachee, &voieG1, &voieG2, SENS_GAUCHE, C_A2, DEVIE,  POUS_A2_DEV,  "Itinéraire G1 GC gauche");
ItinGareVersCachee it_G2_GC_Gauche (&zoneL2, &aiguilles, &gareCachee, &voieG2, &voieG1, SENS_GAUCHE, C_A2, NORMAL, POUS_A2_NORM, "Itinéraire G2 GC gauche");
ItinGareVersCachee it_G1_GC_Droite (&zoneL1, &aiguilles, &gareCachee, &voieG1, &voieG2, SENS_DROITE, C_A1, DEVIE,  POUS_A1_DEV,  "Itinéraire G1 GC droite");
ItinGareVersCachee it_G2_GC_Droite (&zoneL1, &aiguilles, &gareCachee, &voieG2, &voieG1, SENS_DROITE, C_A1, NORMAL, POUS_A1_NORM, "Itinéraire G2 GC droite");

ItinCacheeVersGare it_GC_G1_Gauche (&zoneL1, &aiguilles, &gareCachee, &voieG1, &voieG2, SENS_GAUCHE, C_A1, DEVIE,  POUS_A1_DEV,  "Itinéraire GC G1 gauche");
ItinCacheeVersGare it_GC_G2_Gauche (&zoneL1, &aiguilles, &gareCachee, &voieG2, &voieG1, SENS_GAUCHE, C_A1, NORMAL, POUS_A1_NORM, "Itinéraire GC G2 gauche");
ItinCacheeVersGare it_GC_G1_Droite (&zoneL2, &aiguilles, &gareCachee, &voieG1, &voieG2, SENS_DROITE, C_A2, DEVIE,  POUS_A2_DEV,  "Itinéraire GC G1 droite");
ItinCacheeVersGare it_GC_G2_Droite (&zoneL2, &aiguilles, &gareCachee, &voieG2, &voieG1, SENS_DROITE, C_A2, NORMAL, POUS_A2_NORM, "Itinéraire GC G2 droite");
#else
ItinGareVersCachee it_G1_GC_Gauche (&zoneL2, &aiguilles, &gareCachee, &voieG1, &voieG2, SENS_GAUCHE, C_A2, DEVIE,  POUS_A2_DEV);
ItinGareVersCachee it_G2_GC_Gauche (&zoneL2, &aiguilles, &gareCachee, &voieG2, &voieG1, SENS_GAUCHE, C_A2, NORMAL, POUS_A2_NORM);
ItinGareVersCachee it_G1_GC_Droite (&zoneL1, &aiguilles, &gareCachee, &voieG1, &voieG2, SENS_DROITE, C_A1, DEVIE,  POUS_A1_DEV);
ItinGareVersCachee it_G2_GC_Droite (&zoneL1, &aiguilles, &gareCachee, &voieG2, &voieG1, SENS_DROITE, C_A1, NORMAL, POUS_A1_NORM);

ItinCacheeVersGare it_GC_G1_Gauche (&zoneL1, &aiguilles, &gareCachee, &voieG1, &voieG2, SENS_GAUCHE, C_A1, DEVIE,  POUS_A1_DEV);
ItinCacheeVersGare it_GC_G2_Gauche (&zoneL1, &aiguilles, &gareCachee, &voieG2, &voieG1, SENS_GAUCHE, C_A1, NORMAL, POUS_A1_NORM);
ItinCacheeVersGare it_GC_G1_Droite (&zoneL2, &aiguilles, &gareCachee, &voieG1, &voieG2, SENS_DROITE, C_A2, DEVIE,  POUS_A2_DEV);
ItinCacheeVersGare it_GC_G2_Droite (&zoneL2, &aiguilles, &gareCachee, &voieG2, &voieG1, SENS_DROITE, C_A2, NORMAL, POUS_A2_NORM);
#endif

Itineraire *apItin[NB_ITIN] = {
        &it_G1_GC_Gauche,
        &it_G2_GC_Gauche,
        &it_G1_GC_Droite,
        &it_G2_GC_Droite,
        &it_GC_G1_Gauche,
        &it_GC_G2_Gauche,
        &it_GC_G1_Droite,
        &it_GC_G2_Droite
};


ChefDeGare chef (&aiguilles, &gareCachee, &affichage, &selMode, &boutons, &voieG1, &voieG2, apItin);


void setup() {
        #ifdef DEBUG
        delay (2000);
        Serial.begin (115200);
        Serial.println ("*** DEBUG ***");
        #endif
       
        Tache::setupTaches ();

        led_board.clignoter ();
        gareCachee.rafraichirLcd ();
} // setup


void loop() {
        Tache::loopTaches ();               
} // loop

  • Toutes les classes sont définies dans des fichiers séparés, raison de la multitude d'includes.
  • Tous les objets sont instanciés dans le programme principal par de simples déclarations.
  • Si un objet a besoin d'appeler les méthodes d'un autre, il le fait à partir d'un pointeur lors de l'appel de son constructeur. Par exemple, le constructeur VoieGareCahee reçoit 2 pointeurs vers les 2 objets de la classe Zone qui constituent une voie de gare cachée. Le lien cité dans le 1er post de cette série utilise des références, mais cela ne fonctionne pas pour tous les cas de figure, notamment pour passer un tableau d'objets à un autre objet. J'ai donc préféré les pointeurs plus souples d'emploi.
  • Les constructeurs des classes Zone et Itineraire existent en 2 versions, selon que l'on souhaite activer le mode debug ou non. En mode debug, on peut ainsi passer une chaîne de caractères identifiant l'instance de l'objet. Comme il me reste en encore assez de RAM, je n'ai pas utilisé la directive PROGMEM permettant de placer ces chaînes en mémoire programme.
  • La fonction setup enclenche le mode debug, appelle toutes les méthodes virtuelles setup des objets, puis effectue l'initialisation de la led clignotante et de l'affichage LCD, ces 2 objets ayant besoin d'une initialisation en plus du setup pour des questions de synchronisation au démarrage.
  • La fonction loop ne fait qu'appeler toutes les méthodes virtuelles loop des objets.

Le mode debug
J'ai regroupé tous les #define liés au matériel, ceux d'usage général ainsi que ceux qui enclenchent le mode debug dans le fichier Hardware.h.

// Debug.
//#define DEBUG_BOUTONS_AN
//#define DEBUG_BOUTONS_STATE
//#define DEBUG_MAIN_LOOP
//#define DEBUG_BOUTONS_ROT
//#define DEBUG_ZONE
//#define DEBUG_GC
//#define DEBUG_AIG
//#define DEBUG_AIG_DEM
//#define DEBUG_LCD
//#define DEBUG_CHEF
//#define DEBUG_ITIN


#define DEBUG   (defined DEBUG_MAIN_LOOP || defined DEBUG_BOUTONS_AN || defined DEBUG_BOUTONS_STATE || defined DEBUG_BOUTONS_ROT || \
defined DEBUG_ZONE || defined DEBUG_GC || defined DEBUG_AIG || defined DEBUG_AIG_DEM) || defined DEBUG_LCD || defined DEBUG_CHEF || \
defined DEBUG_ITIN

Chaque instruction #define DEBUG_XXX peut être décommentée pour permettre d'enclencher le mode debug de la classe correspondante. La dernière instruction #define DEBUG ... teste si au-moins un des autres #define DEBUG_XXX est défini. Ainsi, le programme principal peut initialiser la liaison série.

Classe Zone
Le réseau comporte 8 zones de détection: 4 en gare cachée, 2 en gare, 2 en lignes. Les états de l'automate de cette classe sont:
  • E_LIBRE: la zone est libre.
  • E_ENTREE: un train entre dans la zone sans réservation préalable.
  • E_OCCUPEE: un train occupe la zone sans réservation préalable.
  • E_RESERVEE_G: la zone est réservée pour un train tournant dans le sens inverse des aiguilles d'une montre.
  • E_RESERVEE_D: la zone est réservée pour un train tournant dans le sens des aiguilles d'une montre.
  • E_ENTREE_G: un train entre dans la zone dans le sens inverse des aiguilles d'une montre.
  • E_ENTREE_D: un train entre dans la zone dans le sens des aiguilles d'une montre.
  • E_OCCUPEE_G: un train occupe la zone dans le sens inverse des aiguilles d'une montre.
  • E_OCCUPEE_D: un train occupe la zone dans le sens des aiguilles d'une montre.

Le graphe d'états correspondant est:



La réservation préalable des zones est utilisée par les itinéraires. Cela permet de protéger les zones de ligne à voie unique et de déterminer dans quel sens les trains sont en gare. La gare cachée dont les voies comportent 2 zones de détection permettant de déterminer le sens des trains n'a pas besoin de la réservation.

class Zone: public Tache
{
public:
        #ifdef DEBUG_ZONE
        Zone (uint8_t xPin, uint32_t nxDelaiEntree, uint32_t nxDelaiSortie, const char sxNom[]);
        #else
        Zone (uint8_t xPin, uint32_t nxDelaiEntree, uint32_t nxDelaiSortie);
        #endif
       
        void setup ();
        void loop ();

        uint8_t bLibre (void);
        uint8_t bOccupee (void);
        uint8_t bOccupee (uint8_t exSens);
        uint8_t bReservee (void);
        uint8_t bReservee (uint8_t exSens);
        uint8_t bEtatStable (void);
        void reserver (uint8_t exSens);
private:
        const uint8_t Pin;
        const uint32_t nDelaiEntree, nDelaiSortie;
        uint32_t tDebut;

        #ifdef DEBUG_ZONE
        char sNom[32];
        #endif

        uint8_t eEtat;
};

#include "Hardware.h"
#include "Zone.h"


#define E_LIBRE         0
#define E_RESERVE_G     1
#define E_RESERVE_D     2
#define E_ENTREE_G      3
#define E_ENTREE_D      4
#define E_OCCUPEE_G     5
#define E_OCCUPEE_D     6
#define E_ENTREE        7
#define E_OCCUPEE       8



#ifdef DEBUG_ZONE
Zone::Zone (uint8_t xPin, uint32_t nxDelaiEntree, uint32_t nxDelaiSortie, const char sxNom[]) : Pin (xPin), nDelaiEntree (nxDelaiEntree), nDelaiSortie (nxDelaiSortie)
{
        strcpy (sNom, sxNom);
} // Zone::Zone
#else
Zone::Zone (uint8_t xPin, uint32_t nxDelaiEntree, uint32_t nxDelaiSortie) : Pin (xPin), nDelaiEntree (nxDelaiEntree), nDelaiSortie (nxDelaiSortie) {};
#endif


void Zone::setup ()
{
        pinMode (Pin, INPUT_PULLUP);
        eEtat = E_LIBRE;
} // Zone::setup


void Zone::loop ()
{
uint8_t bCapteur;

        bCapteur = digitalRead (Pin);

        switch (eEtat)
        {
                case E_LIBRE:
                        // Zone initialement libre, train détecté sans réservation préalable.
                        if (bCapteur == LOW)
                        {
                                tDebut = millis ();
                                eEtat = E_ENTREE;
                        } // if
                break;

                case E_ENTREE:
                        if ((millis () - tDebut) > nDelaiEntree)
                        {
                                #ifdef DEBUG_ZONE
                                Serial.print (sNom);
                                Serial.println (": occupée");                               
                                #endif

                                tDebut = millis ();
                                eEtat = E_OCCUPEE;
                        } // if
                break;

                case E_RESERVE_G:
                        // Zone libre, mais réservée pour un train tournant à gauche.
                        if (bCapteur == LOW)
                        {
                                tDebut = millis ();
                                eEtat = E_ENTREE_G;
                        } // if
                break;

                case E_ENTREE_G:
                        if ((millis () - tDebut) > nDelaiEntree)
                        {
                                #ifdef DEBUG_ZONE
                                Serial.print (sNom);
                                Serial.println (": occupée G");                               
                                #endif

                                tDebut = millis ();
                                eEtat = E_OCCUPEE_G;
                        } // if
                break;
 
                case E_RESERVE_D:
                        // Zone libre, mais réservée pour un train tournant à droite.
                        if (bCapteur == LOW)
                        {
                                tDebut = millis ();
                                eEtat = E_ENTREE_D;
                        } // if
                break;             
 
                case E_ENTREE_D:
                        if ((millis () - tDebut) > nDelaiEntree)
                        {
                                #ifdef DEBUG_ZONE
                                Serial.print (sNom);
                                Serial.println (": occupée D");                               
                                #endif

                                tDebut = millis ();
                                eEtat = E_OCCUPEE_D;
                        } // if
                break;

                // Ces 3 états peuvent être traités de la même manière.
                case E_OCCUPEE_G:
                case E_OCCUPEE_D:
                case E_OCCUPEE:
                        // Si le train est toujours présent, reset du timer.
                        if (bCapteur == LOW)
                        {
                                tDebut = millis ();
                        }
                        else if ((millis () - tDebut) > nDelaiSortie)
                        {
                                // Plus de train.
                                #ifdef DEBUG_ZONE
                                Serial.print (sNom);
                                Serial.println (": libre");
                                #endif
                               
                                eEtat = E_LIBRE;
                        } // if
                break;
                 
        } // switch
} // Zone::loop


uint8_t Zone::bLibre (void)
{
        return (eEtat == E_LIBRE);       
} // Zone::bLibre


uint8_t Zone::bOccupee (void)
{
        return ((eEtat == E_OCCUPEE) || (eEtat == E_OCCUPEE_G) || (eEtat == E_OCCUPEE_D));
} // Zone::setup


uint8_t Zone::bOccupee (uint8_t exSens)
{
        return (
                ((exSens == SENS_GAUCHE) && (eEtat == E_OCCUPEE_G)) ||
                ((exSens == SENS_DROITE) && (eEtat == E_OCCUPEE_D))
               );
} // Zone::bOccupee


uint8_t Zone::bReservee (void)
{
        return (
                (eEtat == E_RESERVE_G) || (eEtat == E_RESERVE_D)
               );       
} // Zone::bReservee


uint8_t Zone::bReservee (uint8_t exSens)
{
        return (
                ((exSens == SENS_GAUCHE) && (eEtat == E_RESERVE_G)) ||
                ((exSens == SENS_DROITE) && (eEtat == E_RESERVE_D))
               );       
} // Zone::bReservee


uint8_t Zone::bEtatStable (void)
{
        // La zone ne doit pas être dans un état où un train entre,
        // risque de conflit avec le train sortant.
        return (
                (eEtat != E_ENTREE) &&
                (eEtat != E_ENTREE_G) &&
                (eEtat != E_ENTREE_D)
                );                     
} // Zone::bEtatStable


void Zone::reserver (uint8_t exSens)
{
        if (eEtat == E_LIBRE)
        {
                if (exSens == SENS_GAUCHE)
                {
                        #ifdef DEBUG_ZONE
                        Serial.print (sNom);
                        Serial.println (": réservée G");                               
                        #endif
                       
                        eEtat = E_RESERVE_G;
                }
                else if (exSens == SENS_DROITE)
                {
                        #ifdef DEBUG_ZONE
                        Serial.print (sNom);
                        Serial.println (": réservée D");                               
                        #endif
                       
                        eEtat = E_RESERVE_D;
                } // if
        } // if
} // Zone::reserver

La logique de l'automate est répartie entre la méthode loop et les méthodes publiques appelées par les autres objets. Comme toutes les autres classes, celle-ci hérite de la classe Tache.
Cette classe comporte 2 valeurs de timeout qui sont utilisées comme suit:
  • nDelaiEntree: pour la transition des états E_ENTREE... vers les états E_OCCUPEE....
  • nDelaiSortie: pour la transition des états E_OCUPEE... vers l'état E_LIBRE. C'est en particulier ce timeout qui permet de gérer la détection mono alternance du signal DCC.

Ces timeouts permettent également de gérer l'absence de détection sur les derniers wagons des trains, seules les locos sont détectées.

7
Bonjour à tous,

Comme annoncé dans le sujet http://forum.locoduino.org/index.php?topic=98.msg8459#new, voici une présentation de mon réseau hivernal en N.

Description du réseau et des cartes de détection
Le réseau dans son état quasi actuel se présente comme suit. Il s'agit d'une gare sur une voie unique avec une gare cachée permettant le croisement.



Le réseau est découpé en zones comme suit:



  • GC1G: gare cachée 1 gauche
  • GC1D: gare cachée 1 droite
  • GC2G: gare cachée 2 gauche
  • GC2D: gare cachée 2 droite
  • L1: ligne
  • L2: ligne
  • G1: gare voie 1
  • G1: gare voie 2

Le réseau est en DCC. Toutes les zones sont équipées pour détecter les trains par consommation de courant. Les zones des 2 gares sont en plus équipées de diodes pour le freinage ABC. Les aiguilles A1 à A5 sont actionnées par une carte à 8 relais.

Les cartes de détection se composent de diodes pour d'une part assurer la détection de la consommation de courant, d'autre part générer l'asymétrie du signal DCC requise pour le freinage. Ces diodes supplémentaires sont commutées par des relais.



La tension de détection actionne un optocoupleur qui génère le signal logique. Le réseau fonctionnant en DCC, la détection est mono-alternance, c'est ensuite par logiciel que l'on assure la continuité de la détection. L'Arduino, un Mega, tourne suffisamment vite pour ne rien manquer.



Programme
La structure du programme s'inspire des principes décrits ici: https://paulmurraycbr.github.io/ArduinoTheOOWay.html.

Classe Tache
La première classe définit la notion de tâche.

class Tache
{
public:
        static Tache *pListeTaches;
        Tache ();
        static void setupTaches (void);
        static void loopTaches (void);

        virtual void setup (void) = 0;
        virtual void loop (void) = 0;

private:
        Tache *pProchaineTache;
};

Toutes les autres classes du projet héritent de Tache et doivent définir les méthodes setup et loop. Le corps de cette classe est:

#include <Arduino.h>

#include "Tache.h"


Tache *Tache::pListeTaches = NULL;

Tache::Tache ()
{
        pProchaineTache = pListeTaches;
        pListeTaches = this;
} // Tache


void Tache::setupTaches (void)
{
        for (Tache *p = pListeTaches; p; p = p->pProchaineTache)
        {
                p->setup ();
        } // for
} // setupTaches


void Tache::loopTaches (void)
{
        for (Tache *p = pListeTaches; p; p = p->pProchaineTache)
        {
                p->loop ();
        } // for
} // loopTaches

À chaque appel du constructeur, la nouvelle tâche est ajoutée en tête de la liste de tâches. Les méthodes de classe setupTaches et loopTaches appelées respectivement dans les fonctions principales setup et loop parcourent cette liste pour appeler les méthodes virtuelles redéfinies par les classes descendantes de celle-ci.

Cette technique est utilisée entre autres dans les bibliothèques Commanders et Accessories de Thierry et dans ScheduleTable, SlowMotionServo et LightDimmer de Jean-Luc.

Classe Led
Il est toujours intéressant de vérifier visuellement que l'Arduino ne s'est pas planté. Faire clignoter la led de la carte est un bon moyen de vérifier cela. Tant qu'elle clignote, aucune tâche ne bloque le processeur et tout va bien.

#include <stdint.h>

#include "Tache.h"

class LED: public Tache
{                   
public:
        LED (uint8_t xPin, uint32_t nxPeriode);
       
        void setup ();
        void loop ();

        void allumer (void);
        void eteindre (void);
        void clignoter (void);

private:
        const uint8_t   Pin;
        uint8_t         eEtat;
        const uint32_t  nPeriode;
        uint32_t        tDernier;
};

#include "LED.h"


#define E_OFF   0
#define E_ON    1
#define E_BLINK_ON      2
#define E_BLINK_OFF     3

LED::LED (uint8_t xPin, uint32_t nxPeriode) :
        Pin (xPin),
        nPeriode (nxPeriode) {}


void LED::setup (void)
{
        eEtat = E_OFF;
        pinMode (Pin, OUTPUT);
} // LED::setup


void LED::loop (void)
{
uint32_t nDeltaTemps;

        switch (eEtat)
        {
                case E_OFF:
                break;

                case E_ON:
                break;
               
                case E_BLINK_OFF:                       
                        nDeltaTemps = millis () - tDernier;
                        if (nDeltaTemps > nPeriode)
                        {
                                eEtat = E_BLINK_ON;
                                tDernier = millis ();
                        } // if
                break;

                case E_BLINK_ON:
                        nDeltaTemps = millis () - tDernier;
                        if (nDeltaTemps > nPeriode)
                        {
                                eEtat = E_BLINK_OFF;
                                tDernier = millis ();
                        } // if
                break;
        } // switch

        digitalWrite (Pin, (eEtat == E_ON) || (eEtat == E_BLINK_ON));
} // LED::loop


void LED::allumer (void)
{
        eEtat = E_ON;
} // LED::allumer


void LED::eteindre (void)
{
        eEtat = E_OFF;
} // LED::eteindre


void LED::clignoter (void)
{
        if (eEtat == E_OFF)
        {
                eEtat = E_BLINK_OFF;
                tDernier = millis ();
        }
        else if (eEtat == E_ON)
        {
                eEtat = E_BLINK_ON;
                tDernier = millis ();               
        } // if
} // LED::clignoter

Le constructeur reçoit le numéro de la pin et la période de clignotement en paramètre. Tout se passe principalement dans la méthode loop qui implante une machine d'états. On remarque l'importance de gérer le temps à l'aide de millis () et non avec delay (). Il est en effet important de ne pas bloquer dans cette fonction.

Les états possibles sont définis par des instructions #define plutôt qu'avec un enum. Le but était de s'assurer que les états soient codés dans une variable de type uint8_t. Je ne connaissais pas les enum class de C++ 11 et leur possibilité de spécifier le type sous-jacent lorsque j'ai commencé ce projet !

La led s'utilise comme suit dans le fichier .ino.

---
LED led_board (LED_BOARD, PER_CLIGN);
...
void setup ()
{
   ...
   Tache::setupTaches ();

   led_board.clignoter ();
} // setup

Le prochain post décrira les autres classes plus spécifiques à cette application.

Meilleures salutations.

Marc-Henri

8
Bonjour à tous,

Depuis nos derniers échanges au sujet des machines d'états, j'ai avancé, mais sans utiliser l'outil Yakindu Statechart Tool. J'ai réalisé 2 projets Arduino incluant des machines d'états C++.

Gare cachée sur ligne en voie unique
Ce projet a été réalisé pour le réseau d'un ami. La détection des trains se fait par des ILS.
  • Voie: 2 instances.
  • GareCachée: 1 instance.
  • Bouton: 2 instances.
  • Led: 3 instances une pour la led 13 et 2 pour les leds du TCO.

Gestion de mon 2ème réseau en N
Ce 2ème projet, encore en cours, comprend plusieurs machines d'états implantées dans des classes C++.
  • Zone de détection: 8 instances.
  • Boutons: 1 instance de cette classe pour gérer les 7 boutons du TCO connectés à une entrée analogique.
  • Led: 1 instance, pour faire clignoter la led 13 de la carte et indiquer ainsi visuellement que le programme n'est pas planté.
  • Mode: 1 instance de cette classe pour lire le mode de fonctionnement via un contacteur rotatif connecté à une entrée analogique.
  • Aiguilles: 1 instance pour gérer les 5 aiguilles du réseau. Cette classe est responsable du séquencement des commandes des aiguilles afin d'assurer qu'une seule est active à la fois.
  • GareCachée: 1 instance de cette classe inclut 4 instances de zones pour détecter les trains.
  • Affichage: 1 instance de cette classe pour gérer l'affichage LCD pour afficher l'état de la gare cachée et donner d'autres informations à l'utilisateur.
  • Signal: 2 instances de cette classe pour gérer les signaux en sortie de gare.
  • VoieDeGare: 2 instances de cette classe qui incluent 1 instance de zone.
  • Itineraire: 8 instances.
  • ChefDeGare: 1 instance de cette classe qui assure la coordination du tout.

Il y a donc plus d'une vingtaine de machines d'états qui interagissent entre elles. Les classes sous jacentes restent suffisamment simples pour pouvoir être codées directement sans passer par un outil supplémentaire. L'architecture se base sur l'approche décrite ici: https://paulmurraycbr.github.io/ArduinoTheOOWay.html et mentionnée dans mon post: http://forum.locoduino.org/index.php?topic=576.0.

Je posterai plus de détails ultérieurement.

Bon début de semaine et meilleures salutations.

Marc-Henri

9
Débuter / Re : Contact commun à 2 entrées
« le: avril 15, 2019, 10:43:57 pm »
Bonsoir à tous,

Merci pour cette réponse. Je me demande cependant si les diodes sont indispensables.

Meilleures salutations.
Marc-Henri

10
Vos projets / Re : Contrôle numérique des aiguillages
« le: avril 07, 2019, 07:23:30 pm »
Bonjour à tous,

Le problème a été résolu en remplaçant l'affichage LCD à interface parallèle par un modèle équipé d'une interface I2C. Le circuit d'interface I2C étant soudé directement sur l'affichage, les connexions sont réduites au minimum, ce qui réduit d'autant le risque de perturbation. La liaison I2C est longue d'environ 40 cm, réalisée avec un câble audio en profitant du blindage relié à la masse d'un côté.

Plus aucune perturbation n'est apparue en plus d'une heure de fonctionnement du réseau.

Encore un tout grand merci pour toutes les propositions de solution.
Bon début de semaine.

11
Vos projets / Re : Contrôle numérique des aiguillages
« le: avril 02, 2019, 04:57:04 pm »
Bonjour à tous,

J'ai trouvé un post sur le forum Arduino qui présente la solution avec les tores de ferrite.

https://forum.arduino.cc/index.php?topic=316604.0

Mon fournisseur habituel de composants électroniques en Suisse a ce genre d'article. À creuser...

Bonne fin de journée et meilleures salutations.

Marc-Henri

12
Vos projets / Re : Contrôle numérique des aiguillages
« le: avril 02, 2019, 04:43:28 pm »
Bonjour Marcel,

Merci pour ta suggestion.

J'ai déjà essayé de faire fonctionner le système en débranchant l'alimentation des aiguilles. C'est donc un mode où les relais fonctionnent, mais n'ont rien à actionner. Dans ce cas, je n'ai aucune perturbation, raison pour laquelle je recherche plutôt du côté des charges commutées par les relais, les électro-aimants en l'occurrence.

Bonne fin de journée et meilleures salutations.
Marc-Henri

13
Vos projets / Re : Contrôle numérique des aiguillages
« le: avril 02, 2019, 11:16:03 am »
Bonjour Dominique,

Merci beaucoup pour cette suggestion.

Si j'ai bien compris, le tore agit comme une bobine en série qui bloque les hautes fréquences. Idéalement, il faudrait trouver de très vieilles mémoires à tores de ferrite pour récupérer les tores et réaliser ce que tu proposes. Sinon, où trouve-t-on des tores ? Aurais-tu par ailleurs une photo du dispositif ?

Bonne journée et meilleures salutations.
Marc-Henri

14
Vos projets / Re : Contrôle numérique des aiguillages
« le: avril 02, 2019, 08:43:11 am »
Bonjour à tous,

J'ai ajouté les diodes de roue libre, une par électro-aimant, au plus proche des moteurs d'aiguilles, mais sans amélioration significative. Je pense qu'il faudrait pouvoir relier les diodes directement sur les électro-aimants, sans inclure les contacts de fin de course.

Prochains tests prévus:
  • circuit RC série en parallèle sur les contacts des relais de commande des aiguilles, le but étant d'amortir l'arc aux contacts des relais;
  • utilisation d'un affichage LCD I2C que je viens de recevoir;
  • les autres tests suggérés par Dominique: bits inutilisés à la masse, changer l'Arduino.

Bonne journée et meilleures salutations.

15
Vos projets / Re : Contrôle numérique des aiguillages
« le: mars 29, 2019, 04:21:31 pm »
Bonjour Dominique,

Pour reprendre les points que tu soulèves:
  • Changement d'Arduino, à essayer. J'ai une carte Leonardo qui pourrait me permettre de le vérifier.
  • Le LCD est utilisé en mode 4 bits, les 4 autres bits pourraient être à la masse.
  • J'ai déjà un ré-affichage périodique, mais cela ne règle pas tous les cas.
  • Impact sur les variables du programme: je ne pense pas. Lorsque les perturbations surviennent, les variables d'état de la gare cachée ne sont pas modifiées, l'Arduino sait toujours s'il y a des trains ou pas. Actuellement les variables ne sont qu'en RAM, sans sauvegarde en EEPROM, j'en déduis donc qu'elles ne sont pas corrompues. Il n'y a pas non plus de reset intempestif de l'Arduino, une condition que je remarquerais car les aiguilles sont repositionnées par défaut au reset.

Bonne fin de semaine et meilleures salutations.

Pages: [1] 2 3 ... 9