/* Poste de PILOTAGE directement issu de P22 pour le réseau en anneau avec Bluetooth HC-05 comportant
   au choix deux ou trois postes de trois postes de Travail.

  • La génération automatique des JETONS en mode INFINI produit toujours des des JETONs aléatoires pour
    l'adressage de deux ou trois postes de travail, mais gère les retours en fonction de l'indisponibilité
    aléatoire des postes de travail.
    Les modifications sont globalement les suivantes :
    * Sur RESET mise en place du protocole pour tester BP sur A1 en premier et BP sur A3 en second.
      BP sur A1 permet de choisir trois postes de Travail, deux étant par défaut.
    * Modifié les commandes et simplifié les affichages. (Uniquement sur le Moniteur).
    * Mis en place la gestion de la file d'attente et une nouvelle détermination des adresses.

 COMPORTEMENT DU PROGRAMME :
 ---------------------------
  • Sur RESET le programme est en génération manuelle des JETONS via le Moniteur de l'IDE.
    Il attend que l'opérateur saisice "555" ou "666" dans la fenêtre du Moniteur.
    Si durant l'attente de cette directive l'opérateur attend, on voit défiler des ACR
    sous la forme "Retour orphelin > Non."
  • Toutes les trente secondes le programme teste si le dialogue est effectif. Si un JETON spécial
    "999" n'est pas retourné par le réseau, un BIP sonore prévient l'opérateur, la LED triple
    s'allume en jaune et un texte spécifique est présenté sur OLED si Noir n'est pas validé.
    Le système reste figé jusqu'à ce que l'opérateur clique sur le B.P. surveillé par A3.
    Si l'opérateur clique sur B.P. A1, rétablit les affichages OLED si les écrans étaien noirs.
    L'action de l'opérateur éteint la LED triple qui est illuminée en "jaune".
  • Si sur RESET le BP sur A3 est actif, le programme passe en génération automatique des JETONS.
  • Si sur RESET le BP sur A1 est actif, le programme gère trois postes de travail au lieu de deux
    par défaut.
    
  Mode manuel de génération des JETONS :
  ======================================
  • Fermer l'inverseur et activer le Moniteur, le programme attend la synchronisation.
    Frapper "555" ou "666" l'écran affiche "Debut du DIALOGUE".
  • Chaque commande manuelle allume la LED en BLEUE durant 0,8S et OLED rend compte du résultat.
  • Si la commande comporte une erreur la LED ROUGE s'allume et OLED spécifie l'origine et la
    nature de l'erreur. C'est la commande suivante qui éteint la LED ROUGE.
  • Si le JETON ne comporte qu'une seule lettre, c'est une directive.
    La directive '?' ou 'H' fait lister les commandes possibles sur OLED.
    Si 'S' est active, la lettre 'S' est affichée dans un petit cadre en haut à droite.
    La sortie de la commande 'N' fait afficher le texte du HELP.

 BRANCHEMENTS à effectuer :
---------------------------
  • Le +5V va sur VCC des deux HC-05.
  • GND va sur GND des deux HC-05.
  • GND va sur EN des deux HC-05.
  • D0 (RX) d'Arduino va sur TXD du HC-05 d'Entrée.
  • D1 (TX) d'Arduino va sur RXD (1k,2,2k) du HC-05 d'Entrée.
  • D2 d'Arduino va sur TXD du HC-05 de Sortie.
  • D3 d'Arduino va sur RXD (1k,2,2k) du HC-05 de Sortie.
  • D9 pilote un relais 2xRT 5V si mode manuel (Développement) est envisagé.
    Les contacts repos sont en série avec l'inverseur double qui isole D0 et D1.
  • A1 va sur un bouton poussoir type "Travail" dont le point froid est sur GND.
    A1 permet d'allumer ou d'éteindre les écrans OLED en mode INFINI.
  • A3 va sur un bouton poussoir type "Travail" dont le point froid est sur GND.
    A3 permet en autonome de passer en mode en mode INFINI.
    
  • A4 va sur SDA de l'afficheur OLED de 1,3 pouces de diagonale.
  • A5 va sur SLC de l'afficheur OLED.

Compilateur v1.7.9.          PGM                RAM dynamique
Developpement :        22252 Octets (72%)      1081 Octets (52%)
Utilisation :          21634 Octets (70%)      1081 Octets (52%) */

//------------------------ Constantes du programme --------------------------
#define Version "11/11/2025" // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Taille_PGM "21634 72% 1081 52%" // Tailles du PGM @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Lgr_JETON 7 // Longueur maximale d'un ACR Esclave @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Developpement true // Si false limite les affichages USB en automatique. @@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Duree_Time_OUT 4000 // Délai d'attente pour conclure "Orphelin Non" @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
// La valeur ci-dessus a été augmentée jusqu'à ce que le mode automatique ne se bloque plus.

#define CR 13 // Code du Retour Chariot qui vaut 13 codé en ASCII sous forme décimale.
#define LF 10 // Code du passage à la ligne qui vaut 10 codé en ASCII sous forme décimale.

//------------------- Définition des broches binaires -----------------------
#define RXD 2 // D2 va sur TX du module Bluetooth de Sortie.
#define TXD 3 // D3 va sur RX du module Bluetooth de Sortie avec pont de résistances.
#define BUZZER 4 // Va sur le bruiteur actif.
#define LED_triple_ROUGE 6
#define LED_triple_VERTE 7
#define LED_triple_BLEUE 8
#define Relais 9 // Relais provisoire utilisé pour le développement.
#define LED_Arduino 13

//------------------- Définition des broches analogiuqes --------------------
#define BP_sur_A1 15 // A1 sert à allumer ou éteindre OLED en mode INFINI.
#define BP_sur_A3 17 // A3 sert à libérer une alerte de perte de réseau.
                             // Sur RESET A3 actif fait passer en mode INFINI.

//---------------------- Déclarations pour le matériel ----------------------
// Cette section est placée après la déclaration des broches binaires qui doivent
// être définies lors de l'instruction SoftwareSerial mySerial(RX,TX);
#include <SoftwareSerial.h>
SoftwareSerial HC05_de_Sortie(RXD,TXD);

#include "U8glib.h"
U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_NONE); // Usage de la ligne I2C (Two Wire Interface)

//------------------------- Variables du programme --------------------------
boolean A_empiler, Aff_ignore, Allume, INFINI, Led_Arduino, Noir, OK, Silence, Un_sur_deux;
char ACTION, Adresse_Poste, CAR_HC05_de_Sortie, CAR_USB, SAV_Index;
byte Index, Nb_dans_la_PILE, Nb_postes_de_Travail, Nb_Valides;
byte Un_sur_5 = 0;
unsigned long Compteur;
boolean Poste_disponible[4];
char JETON[Lgr_JETON];
char SAV_JETON[Lgr_JETON];
char PILE_Orphelins[9]; // Prévue pour jusqu'à trois postes de travail.
unsigned long Temps_pour_le_clignotement = 0;
unsigned long Temps_pour_tester_le_dialogue;
unsigned long TIME_OUT;

//========================= Procédures de servitude ============================

void BIP() {
  if (!Silence) {digitalWrite(BUZZER, HIGH); delay(100); digitalWrite(BUZZER, LOW);}}

void Inverser_LED_Arduino() {digitalWrite(LED_Arduino,!digitalRead(LED_Arduino));}

void Eteindre_la_LED_triple() {
  digitalWrite(LED_triple_ROUGE,LOW); digitalWrite(LED_triple_VERTE,LOW);
  digitalWrite(LED_triple_BLEUE,LOW);}

void Vider_le_tampon_HC05() {while (HC05_de_Sortie.available()) HC05_de_Sortie.read();}

void Initialise_les_sorties() {
  pinMode(RXD,INPUT); pinMode(TXD,OUTPUT); pinMode(LED_Arduino,OUTPUT); pinMode(LED_triple_ROUGE,OUTPUT);
  pinMode(LED_triple_VERTE,OUTPUT); pinMode(LED_triple_BLEUE,OUTPUT); Eteindre_la_LED_triple();
  pinMode(Relais,OUTPUT); digitalWrite(Relais,HIGH);
  pinMode(BUZZER,OUTPUT); digitalWrite(BUZZER,LOW);
  pinMode(BP_sur_A1,INPUT);} // Broche en entrée et R PULLUP sur le circuit imprimé.

void Affiche_JETON_sur_USB() {for (byte I = 0; I < Index; I++) Serial.print(JETON[I]);}

void Liste_retour_sur_USB() {
     Serial.println();
     Serial.print(F(" Cmptr = ")); Serial.print(Compteur);
     Serial.print(F(" Nvalide=")); Serial.print(Nb_Valides); Serial.print(F(" Npile="));
     Serial.print(Nb_dans_la_PILE); Serial.print(F(" Retour orphelin = "));
     if (Index == 0) Serial.print(F("Non.")); else Affiche_JETON_sur_USB(); Serial.println();}

void Liste_Disponibles() {
  Serial.print('['); Serial.print(Poste_disponible[0]); Serial.print(Poste_disponible[1]);
  if (Nb_postes_de_Travail == 3) Serial.print(Poste_disponible[2]);
  Serial.print('D'); Serial.print(']');}

void Aff_le_registre_attente() {
  Serial.print(F(" > ["));
  for (byte I = 0; I < 3; I++) Serial.print(PILE_Orphelins[I]); Serial.print(F("] ["));
  for (byte I = 3; I < 6; I++) Serial.print(PILE_Orphelins[I]); Serial.print(F("] "));  
  if (Nb_postes_de_Travail == 3) {
     Serial.print('['); for (byte I = 6; I < 9; I++) Serial.print(PILE_Orphelins[I]); Serial.print("] ");}
  Serial.print(F("Disponibles = "));
  Liste_Disponibles();}

void Aff_la_PILE(byte Option) {
  if (Developpement) {
     Serial.println(); Serial.print(' ');
     switch (Option) {
        case 0 : {Serial.print(F("INIT. ")); break;}
        case 1 : {Serial.print(F("Em")); break;}
        case 2 : {Serial.print('D'); Serial.print(char(233));}}
     Serial.print(F("pile ")); if (Option != 0) Serial.print (JETON[0]); Aff_le_registre_attente();}}

void Empiler_un_JETON() {
  Nb_dans_la_PILE++; Nb_Valides = Nb_postes_de_Travail - Nb_dans_la_PILE; Un_sur_deux = false;
  switch(JETON[0]) {
    case '1' : {PILE_Orphelins[0] = JETON[0]; PILE_Orphelins[1] = JETON[1];
                PILE_Orphelins[2] = JETON[2]; break;}
    case '2' : {PILE_Orphelins[3] = JETON[0]; PILE_Orphelins[4] = JETON[1];
                PILE_Orphelins[5] = JETON[2]; break;}
    case '3' : {PILE_Orphelins[6] = JETON[0]; PILE_Orphelins[7] = JETON[1];
                PILE_Orphelins[8] = JETON[2]; break;}}
  Poste_disponible[byte(JETON[0]) - 49] = false; Aff_la_PILE(1);} // Invalide le poste de travail.

// NOTE : byte(JETON[0]) - 49 se retrouve cinq fois dans le programme. Le placer en fonction a été
//        testé, et s'avère plus couteux en octets.

void Traiter_un_retour_orphelin() {
    //------------ Ignorer FIN, DEB, 555, 666 ou 999 -------------
     if ((JETON[0] == 'F') || (JETON[0] == 'D') || (JETON[0] == '9')|| (JETON[2] == 'E')
        || (JETON[0] == '5')|| (JETON[0] == '6')) A_empiler = false;
     //----------- Ignorer Erreur mais valider le poste -----------
     if (JETON[2] == 'E') {digitalWrite(LED_triple_ROUGE,HIGH);
                           if (Poste_disponible[byte(JETON[0]) - 49] == false) {
                              Poste_disponible[byte(JETON[0]) - 49] = true;
                              Nb_dans_la_PILE--; Nb_Valides = Nb_postes_de_Travail - Nb_dans_la_PILE;}}
     //------------ Ignorer une ADR inconnue. (3 ou 4) ------------
     if (JETON[0] == char(Nb_postes_de_Travail + 49)) A_empiler = false;
     //------------------------------------------------------------
     if (!A_empiler && Aff_ignore) Aff_ignore_le_JETON(); // Affichage si developpement.
     //------------------------------------------------------------
     if (A_empiler && (Poste_disponible[byte(JETON[0]) - 49]) && Index > 0) {
        if (Developpement) {Serial.print(F(" Invalide poste ")); Serial.print(JETON[0]);}
        Empiler_un_JETON();}}

void Regarde_si_JETON_orphelin_sur_USB() {// Donc arrivant du Moniteur ou du Bluetooth Esclave n°3.
  // Si le tampon USB contient du texte, réceptionne ce dernier dans JETON[].
  A_empiler = true; Aff_ignore = true;
  if (Developpement) digitalWrite(Relais,LOW); //Rétablit TX,RX du HC-05 d'entrée.
  digitalWrite(LED_triple_BLEUE, HIGH); delay(150); digitalWrite(LED_triple_BLEUE, LOW);
  JETON[2] = 'e'; // Pour que les JETONS 1:E, 2:E etc ne déclenchent pas un BIP d'erreur.
  Index = 0; TIME_OUT = millis(); // Débute le chronométrage du TIME_OUT.
  //======================= Attente d'un JETON ====================
  while (!(Serial.available()) && (millis() - TIME_OUT < Duree_Time_OUT));
  if (millis() - TIME_OUT >= Duree_Time_OUT) {A_empiler = false; Aff_ignore = false;}
  //================ Si présent capturer le JETON =================
  while (Serial.available()) {CAR_USB = Serial.read();
         if ((Index < Lgr_JETON) && (CAR_USB != CR) && (CAR_USB != LF)) {
            //---------- Passer les lettre en MAJUSCULES ----------
            if ((CAR_USB >= 'a') && (CAR_USB < char(123))) CAR_USB = CAR_USB & 223;
            // Bien moins couteux en octets que Chaine_Memorisee.toUpperCase().
            //-----------------------------------------------------
            JETON[Index] = CAR_USB; Index++;} delay(5);} // Délai impératif.
  //===============================================================
  if (Developpement) Liste_retour_sur_USB();
  //=========== Traiter le cas où un JETON est arrivé =============
  if (Index > 0) Traiter_un_retour_orphelin(); // Un JETON orphelin est revenu du réseau.
  // Ci-dessus : Invalide le poste de travail, Nb_dans_la_PILE++ et Nb_Valides--.
  //========== Traiter le cas si pas de JETON orphelin ============
  else {if (((byte(JETON[0]) - 48) < (Nb_postes_de_Travail + 1)) &&
           (Poste_disponible[byte(JETON[0]) - 49] == false)) { //  Valider le poste si concerné :
           Poste_disponible[byte(JETON[0]) - 49] = true; Nb_dans_la_PILE--;
           Nb_Valides = Nb_postes_de_Travail - Nb_dans_la_PILE;}}
  //===============================================================
  if (!INFINI) digitalWrite(Relais,HIGH);}
  // Ci-dessus : Isole TX,RX du HC-05 d'entrée même si Developpement = false ce qui évite un test inutile.}

void Presentation_sur_OLED(byte Position) {
  u8g.firstPage(); do {// Début de la boucle d'affichage de la page écran.
      u8g.drawFrame(0,0,128,46); u8g.setScale2x2();
      u8g.setPrintPos(10,10); u8g.print("Poste de");
      u8g.setPrintPos(10,20); u8g.print("PILOTAGE"); u8g.undoScale();
      u8g.setPrintPos(Position,58); u8g.print("Version du "); u8g.print(Version);}
  while(u8g.nextPage());}

void Page_de_presentation() {
  for (char I = 127; I > -100; I = I - 4) Presentation_sur_OLED(I);}

void Aff_Att_SYNCHRO_sur_OLED() {
   u8g.firstPage(); do {// Début de la boucle d'affichage de la page écran.
      u8g.drawFrame(17,0,105,34); u8g.drawFrame(19,2,101,30); u8g.setPrintPos(40,14);
      u8g.print("Attente de"); u8g.setPrintPos(25,26); u8g.print("SYNCHRONISATION");
      u8g.setPrintPos(34,45); u8g.print("Nb postes de "); u8g.setPrintPos(37,58);
      u8g.print("Travail = "); u8g.print(Nb_postes_de_Travail);}
  while(u8g.nextPage());}

void Attente_de_la_synchronisation() {
  boolean Pair = false;
  Serial.println(); Serial.println(F(" Attente de la SYNCHRONISATION."));
  //--------------- Affichage sur OLED durant 3,5 secondes ---------------
  Aff_Att_SYNCHRO_sur_OLED(); delay(3500);
  //------------ Envoyer "555" jusqu'à réception de ce texte /------------
  do {Compteur++; Vider_le_tampon_HC05();  // Changer l'affichage pour visualiser les tentatives :
     if (Pair) HC05_de_Sortie.println("555"); else HC05_de_Sortie.println("666"); Pair = !Pair;
     Regarde_si_JETON_orphelin_sur_USB();
     //-------------------------------------------------------------------
     // Transcoder pour simplifier le test de fin de la boucle while.
     if (JETON[0] == '6') JETON[0] = '5'; if (JETON[1] == '6') JETON[1] = '5';
     if (JETON[2] == '6') JETON[2] = '5';}
  while ((JETON[0] != '5') || (JETON[1] != '5') || (JETON[2] != '5'));
  //--------------- Signale le début du dialogue sur OLED ----------------
  u8g.firstPage(); do {// Début de la boucle d'affichage de la page écran.
    u8g.setPrintPos(40,25); u8g.print("Debut du");
    u8g.setPrintPos(40,37); u8g.print("DIALOGUE");}
  while(u8g.nextPage()); Temps_pour_tester_le_dialogue = 0;}

void Aff_NB_postes_valides() {
  u8g.drawVLine(99,1,13); u8g.setPrintPos(102,10); u8g.print('T'); u8g.print(Nb_Valides);}

void AFF_PILE() {
  u8g.drawVLine(89,15,15); u8g.drawVLine(102,15,15); u8g.drawVLine(115,15,15);
  if (!Poste_disponible[0]) {u8g.setPrintPos(94,25); u8g.print('1'); u8g.drawFrame(90,15,12,15);}
  if (!Poste_disponible[1]) {u8g.setPrintPos(107,25); u8g.print('2'); u8g.drawFrame(103,15,12,15);}
  if (Nb_postes_de_Travail == 2) {u8g.drawLine(116,15,128,31); u8g.drawLine(116,29,128,12);}
  else {if (!Poste_disponible[2]) {u8g.setPrintPos(119,25); u8g.print('3'); u8g.drawFrame(116,15,11,15);}}}

void Ecran_OLED_en_exploitation() {// Traite l'affichage d'une erreur éventuelle.
  if (JETON[2] == 'E')  {BIP(); digitalWrite(LED_triple_ROUGE,HIGH);}
  u8g.firstPage(); do {
    if (!Noir) {
       u8g.drawVLine(115,1,13); Aff_NB_postes_valides(); AFF_PILE(); u8g.setPrintPos(119,10);
       if (Silence) u8g.print('S'); else {u8g.drawLine(116,1,127,13); u8g.drawLine(116,13,127,1);}
       Aff_cadre_a_quatre_lignes(); u8g.setPrintPos(10,10); u8g.print("Cmptr = "); u8g.print(Compteur);
       u8g.setPrintPos(10,26); u8g.print("JETON  : ");
       for (byte I = 0; I < SAV_Index; I++) u8g.print(SAV_JETON[I]);
       u8g.setPrintPos(10,42); u8g.print("Retour : ");
       if (Index > 0) for (byte I = 0; I < Index; I++) u8g.print(JETON[I]);
       else u8g.print("Non");
       if (JETON[2] == 'E') {u8g.drawVLine(89,31,16); u8g.setPrintPos(83,42);
          u8g.print("  E / "); u8g.print(JETON[0]); u8g.setPrintPos(10,58);
          switch(JETON[1]) {
            case '1' : {u8g.print("Lgr incorrecte."); break;}
            case '2' : {u8g.print("Action invalide."); break;}
            case '3' : u8g.print('S'), u8g.print(char(233)); u8g.print("parateur non ':'");}}
       else {if (INFINI) {u8g.setPrintPos(8,58); u8g.print(">>> Mode INFINI <<<");}
             else {u8g.setPrintPos(8,58); u8g.print(">>> Mode MANUEL <<<");}}}}
    while(u8g.nextPage());}

void Orange_sur_LED_triple() {
  if (Allume) {digitalWrite(LED_triple_ROUGE, HIGH); digitalWrite(LED_triple_VERTE, HIGH);}
  else Eteindre_la_LED_triple();}

void Double_cadre() {u8g.drawFrame(21,13,95,34); u8g.drawFrame(17,10,103,40);}

void Aff_reprise_sur_OLED() {
  u8g.firstPage(); do {
    if (!Noir) {Double_cadre();
       u8g.setPrintPos(31,26); u8g.print("Reprise de la");
       u8g.setPrintPos(29,40); u8g.print("COMMUNICATION.");}}
  while(u8g.nextPage());}

void Avertissement_sur_OLED() {
  u8g.firstPage(); do {
    if (!Noir) {Double_cadre();
       u8g.setPrintPos(39,26); u8g.print("PROBLEME de "); u8g.drawLine(70,17,71,18);
       u8g.setPrintPos(29,40); u8g.print("COMMUNICATION.");}}
  while(u8g.nextPage());}

void Teste_le_reseau() {
  if (Developpement) Serial.println(F(" TEST du RESEAU."));
  Compteur++; Vider_le_tampon_HC05(); HC05_de_Sortie.println("999");
  SAV_Index = 3; SAV_JETON[0] = '9'; SAV_JETON[1] = '9';  SAV_JETON[2] = '9';
  Regarde_si_JETON_orphelin_sur_USB();
  OK = ((JETON[0] == '9') && (JETON[1] == '9') && (JETON[2] == '9'));}

void Aff_ignore_le_JETON() {
    if (Developpement) {Serial.println(); Serial.print(F(" JETON ignor"));
        Serial.print(char(233)); Serial.println('.');}}
   
void Deuxieme_test() {
  if (Developpement) {
     Serial.println(); Serial.print(F(" Deuxi")), Serial.print(char(232)); Serial.print(F("me"));} 
  Teste_le_reseau(); digitalWrite(LED_Arduino,LOW);  
  if (!OK) {
     Avertissement_sur_OLED(); Temps_pour_le_clignotement = millis();
     //------------------ Attendre un clic sur le BP de A3 -------------------
     while (analogRead(BP_sur_A3) > 511) { // Attendre l'activation.
           //================= Chronométrage pour le clignotement ================
           if (millis() - Temps_pour_le_clignotement > 75)  {
              Temps_pour_le_clignotement = millis(); Allume = !Allume; Orange_sur_LED_triple();}
           //=====================================================================
           //-------------------- Tester le BP sur A1 --------------------
           if (analogRead(BP_sur_A1) < 512) { // Active OLED si noir.
              digitalWrite(LED_Arduino,HIGH);
              while (analogRead(BP_sur_A1) < 512); // Attendre le relâcher.
              Noir = false; digitalWrite(LED_Arduino,LOW);}}
           //-------------------------------------------------------------
     Eteindre_la_LED_triple(); digitalWrite(LED_triple_BLEUE,HIGH);
     while (analogRead(BP_sur_A3) < 511);{ // Attendre la libération.
     digitalWrite(LED_triple_BLEUE,LOW);}}
  else {Ecran_OLED_en_exploitation();  Aff_ignore_le_JETON();}
  Eteindre_la_LED_triple(); digitalWrite(LED_Arduino,LOW); Aff_reprise_sur_OLED(); }

void Tester_le_dialogue() {
   if (Developpement) {Serial.println(); Serial.print(F(" Premier"));}
   Teste_le_reseau(); Ecran_OLED_en_exploitation(); if (!OK) Deuxieme_test();}

void Aff_cadre_a_quatre_lignes() {
  u8g.drawFrame(0,0,128,64); u8g.drawHLine(1,14,126); u8g.drawHLine(1,30,126); u8g.drawHLine(1,47,126);}

void Attendre_une_commande_sur_USB() {
  //----------- Effectuer une SAISIE sur le Moniteur -----------
  Index = 0;
  while (!Serial.available()) { // Attente d'une saisie sur le Moniteur.
        //================= Chronométrage pour le clignotement ================
        if (millis() - Temps_pour_le_clignotement > 75)  {
           Temps_pour_le_clignotement = millis(); Inverser_LED_Arduino();}}
        //=====================================================================
  digitalWrite(LED_triple_ROUGE,LOW); // Annule l'alerte rouge si erreur sur JETON précédent.
  while (Serial.available()) {CAR_USB = Serial.read();
        if ((Index < Lgr_JETON) && (CAR_USB != CR) && (CAR_USB != LF)) {
           //---------- Passer les lettre en MAJUSCULES ----------
           if ((CAR_USB >= 'a') && (CAR_USB < char(123))) CAR_USB = CAR_USB & 223;
              // Bien moins couteux en octets que Chaine_Memorisee.toUpperCase().
              //-----------------------------------------------------
              if (Index < 5) {JETON[Index] = CAR_USB; Index++;} delay(5);}} // Délai impératif.
  for (byte I = 0; I < Index; I++) SAV_JETON[I] = JETON[I]; SAV_Index = Index;}

int SRAM_LIBRE() { // Fonction qui retourne la taille de SRAM disponible.
    extern int __heap_start, *__brkval; // Déclaration des deux pointeurs dédiés.
    byte BIDON; // Dernière variable allouée, donc occupe le "haut" du TAS.
    if (__brkval == 0) {return (int) &BIDON -(int) &__heap_start;}
    else {return (int) &BIDON -(int) __brkval;}}

void Aff_caracteristiques_du_demonstrateur() {
  if (!Noir) {
     u8g.firstPage(); do {Aff_cadre_a_quatre_lignes();
        u8g.setPrintPos(15,10); u8g.print("Rev du "); u8g.print(Version);
        u8g.setPrintPos(15,26); u8g.print(Taille_PGM);
        u8g.setPrintPos(15,42); u8g.print("PILE - TAS = "); u8g.print(SRAM_LIBRE());
        u8g.setPrintPos(15,58); u8g.print("Compilateur 1.7.9");}
  while(u8g.nextPage());}}

void Afficher_les_commandes() {
  Serial.println(); Souligner(); 
  Serial.print(F("  >>>> Commande D : DEBUT des Indisponibilit")); Serial.print(char(233));
  Serial.println("s."); Serial.print(F("  >>>> Commande E : R")); Serial.print(char(233));
  Serial.println(F("seau ETENDU OUI/NON")); Serial.print(F("  >>>> Commande F : FIN des Indisponibilit"));
  Serial.print(char(233)); Serial.println(F("s.")); Serial.println(F("  >>>> Help avec 'H','?' ou ','."));
  Serial.println(F("  >>>> Commande I : INFINI.")); Serial.println(F("  >>>> Commande N : Ecran Noir."));
  Serial.println(F("  >>>> Commande P : Programme."));
  Serial.println(F("  >>>> Commande R : Etat du REGISTRE d'attente.")); 
  Serial.println(F("  >>>> Commande S : Silence OUI/NON."));
  Serial.print(F("  >>>> Commande T : Test du r")); Serial.print(char(233)); Serial.println(F("seau.")); 
  Serial.println(F("  >>>> Commande V : Restore les ECRANS."));
  Serial.println(F("  >>>> Commande Z : Compteur = 0.")); Souligner();}

void RX_consigne_A_F() {
    Regarde_si_JETON_orphelin_sur_USB(); Ecran_OLED_en_exploitation();}

void AFF_init_indisponibilite() {
  Serial.print(F(" l'indisponibilit")); Serial.print(char(233));
  Serial.print(F(" al")); Serial.print(char(233)); Serial.print(F("atoire."));}

void Suspendre_les_indisponibilites() {
  Serial.print(F(" Premier JETON de garde :")); 
  Regarde_si_JETON_orphelin_sur_USB();
  Serial.println(); Serial.print(F(" Suspend")); AFF_init_indisponibilite(); Compteur++;
  HC05_de_Sortie.println("FIN"); SAV_Index = 3; SAV_JETON[0] = 'F'; SAV_JETON[1] = 'I';
  SAV_JETON[2] = 'N'; RX_consigne_A_F();}

void Active_les_indisponibilites() {
  Serial.println(); Serial.print(F(" Valide")); AFF_init_indisponibilite();
  Compteur++; HC05_de_Sortie.println("DEB"); SAV_Index = 3; SAV_JETON[0] = 'D'; SAV_JETON[1] = 'E';
  SAV_JETON[2] = 'B'; RX_consigne_A_F();}

void Aff_NB_poste_de_travail() {
  Serial.println(); Serial.print(F(" >>>> Nb postes de Travail = ")); Serial.println(Nb_postes_de_Travail);}

void Traiter_la_Commande() {
  CAR_USB = SAV_JETON[0];
  if (CAR_USB == ',') CAR_USB = 'H'; if (CAR_USB == '?') CAR_USB = 'H';
  switch (CAR_USB) {
    case 'D' : {Active_les_indisponibilites(); break;}
    case 'E' : {if (Nb_postes_de_Travail == 2) Nb_postes_de_Travail = 3;
                else Nb_postes_de_Travail = 2; Aff_NB_poste_de_travail(); break;}
    case 'F' : {Suspendre_les_indisponibilites(); break;}
    case 'H' : {Afficher_les_commandes(); break;}
    case 'I' : {Mode_automatique(); break;}
    case 'N' : {Noir = !Noir; Ecran_OLED_en_exploitation(); break;}
    case 'P' : {Aff_caracteristiques_du_demonstrateur(); break;}
    case 'R' : {Serial.println(); Serial.print(F(" Registre d'attente "));
                Aff_le_registre_attente(); break;}
    case 'S' : {Silence = !Silence; Ecran_OLED_en_exploitation(); break;}
    case 'T' : {Tester_le_dialogue(); break;}
    case 'V' : {Serial.println(); Serial.println(F(" Eteindre toutes les LEDs."));
                Eteindre_les_LEDs_sur_les_postes_de_travail(); Serial.println(); 
                Serial.println(F(" Allumer les Ecrans OLED.")); Serial.println();
                Active_les_Ecrans_poste_de_Travail(); break;}
    case 'Z' : {Compteur = 0; break;}
    default : {BIP(); Serial.println(); Serial.println(F(" Commande incorrecte."));}}}

void Affiche_mode_INFINI_sur_OLED() {
  u8g.firstPage(); do {u8g.drawFrame(0,0,128,64); u8g.drawFrame(3,3,122,58);
     u8g.setPrintPos(33,18); u8g.print("ATTENTION :");
     u8g.setPrintPos(22,34); u8g.print("Passe en mode");
     u8g.setPrintPos(44,51); u8g.print("INFINI.");}
  while(u8g.nextPage());}

void Souligner() {Serial.println(F(" ----------------------------------"));}

void Presenter_le_PGM_sur_USB() {
  Serial.println(); Serial.print(F(" Poste de PILOTAGE : Version du ")); Serial.println(Version);
  Souligner(); Serial.println(F(" Gestion des postes Indisponibles."));
  Souligner(); Serial.println(F(" Version de developpement pour"));
  Serial.println(F(" proposer des JETONs manuellement."));  Souligner();
  Serial.println(F(" * Le relais d'isolation Fig.80 est en place."));
  Serial.println(F(" 1) Inverseur Fig.80 sur PGM.")); Serial.println(F(" 2) Televerser le programme."));
  Serial.println(F(" 3) Inverseur Fig.80 sur UT.")); 
  Serial.println(F(" 4) Activer le Moniteur : Le relais est au travail."));
  Serial.println(F(" 5) Attendre l'affichage >>>>> Debut du dialogue. <<<<<"));
  Serial.print(F(" 6) Saisir librement des JETONS."));
  Afficher_les_commandes();}

void TX_un_JETON(String JETON) {
  HC05_de_Sortie.println(JETON); Compteur++;
  Regarde_si_JETON_orphelin_sur_USB(); Ecran_OLED_en_exploitation();
  digitalWrite(LED_triple_ROUGE,LOW); // Annule une erreur précédente en éteignant la LED triple rouge.
  delay(500);}

void Eteindre_les_Ecrans() {
  TX_un_JETON("1:A"); TX_un_JETON("2:A"); TX_un_JETON("3:A");}

void Active_les_Ecrans_poste_de_Travail() {
  TX_un_JETON("1:B"); TX_un_JETON("2:B"); TX_un_JETON("3:B");}

void Eteindre_les_LEDs_sur_les_postes_de_travail() {
  TX_un_JETON("1:D"); TX_un_JETON("2:D"); TX_un_JETON("3:D");}

//================== Procédures du mode automatique : Génération des JETONs aléatoires ==================
void Initialise_le_Registre_ATTENTE() {
  PILE_Orphelins[0] = '1'; PILE_Orphelins[1] = '1'; PILE_Orphelins[2] = '1';
  PILE_Orphelins[3] = '2'; PILE_Orphelins[4] = '2'; PILE_Orphelins[5] = '2';
  PILE_Orphelins[6] = '3'; PILE_Orphelins[7] = '3'; PILE_Orphelins[8] = '3';}

void Affichage_du_chrono_pour_tester_A3() {
  byte Decompteur = 5; Temps_pour_tester_le_dialogue = millis(); // Double utilisation de ce chronomètre.
  while (millis() - Temps_pour_tester_le_dialogue < 5000) {
    u8g.firstPage(); do {u8g.drawFrame(0,0,128,64); u8g.drawFrame(3,3,122,58);
                        u8g.setPrintPos(15,18); u8g.print("Si INFINI voulu");
                        u8g.setPrintPos(21,34); u8g.print("Activer BP/A3.");
                        u8g.setPrintPos(26,51); u8g.print("Chrono : "); 
                        u8g.setScale2x2(); u8g.setPrintPos(38,26); u8g.print(Decompteur); u8g.undoScale();}
    while(u8g.nextPage()); delay(1000); Decompteur--;}}

void Selectionne_un_poste() {
  byte Poste;
  do Poste = random(1,Nb_postes_de_Travail + 2); // +2 car il faut MAX+1 avec la fonction random.
  while (Poste_disponible[Poste - 1] == false);
  if (Poste == Nb_postes_de_Travail + 1) {
     Un_sur_5++; if (Un_sur_5 == 5) Un_sur_5 = 0;
                 else Poste = random(1,4);}
  Adresse_Poste = char(Poste + 48); // Transforme le byte en char.
  if (Developpement) { Serial.println(); Serial.print(F(" Adresse ")); Serial.print(Adresse_Poste);
     Serial.print(F(" choisie dans Disponibles")); Liste_Disponibles(); }}

void Selectionne_une_action() {// Génère de 3 pour 'C' à 11 pour 'K'.
  byte Chiffre;
  Chiffre = random(3,12); // 1 et 2 éliminés pour ne pas agir sur Noir/Écran en générant 'A' et 'B'. 
  ACTION = char(Chiffre + 64);} // Transforme le Chiffre en lettre majuscule.

void Genere_un_JETON_aleatoire_pour_un_poste_valide() {
  Index = 3; Selectionne_un_poste(); Selectionne_une_action(); // Impérativement dans cet ordre.
  JETON[0] = Adresse_Poste; JETON[1] = ':'; JETON[2] = ACTION;
  SAV_Index = 3; SAV_JETON[0] = JETON[0]; SAV_JETON[1] = JETON[1]; SAV_JETON[2] = JETON[2];}

void Transmet_un_JETON() {
  for (byte I = 0; I < Index; I++) HC05_de_Sortie.write(JETON[I]);
  HC05_de_Sortie.write(CR); HC05_de_Sortie.write(LF); Compteur++;
  if (Developpement) {Serial.println(); Serial.print(F(" JETON Transmis = ")); Affiche_JETON_sur_USB();}}

void Reception_sur_reseau() {
  Regarde_si_JETON_orphelin_sur_USB(); Ecran_OLED_en_exploitation();}
  
void TX_JETON_et_Aff_orphelin_sur_OLED() {Transmet_un_JETON(); Reception_sur_reseau();}

void Depiler_un_JETON() {
  byte Index_PILE;
  //--------------- Rechercher dans la PILE le premier invalide ---------------
  Index_PILE = 0; while (Poste_disponible[Index_PILE]) Index_PILE++;
  //----------------------- Pointer le premier invalide -----------------------
  Index = Index_PILE * 3;
  //---------------------------------------------------------------------------
  switch(Index_PILE) {
    case 0 : {JETON[0] = PILE_Orphelins[0]; JETON[1] = PILE_Orphelins[1];
                JETON[2] = PILE_Orphelins[2]; break;}
    case 1 : {JETON[0] = PILE_Orphelins[3]; JETON[1] = PILE_Orphelins[4];
                JETON[2] = PILE_Orphelins[5]; break;}
    case 2 : {JETON[0] = PILE_Orphelins[6]; JETON[1] = PILE_Orphelins[7];
                JETON[2] = PILE_Orphelins[8]; break;}}
  SAV_Index = 3; SAV_JETON[0] = JETON[0]; SAV_JETON[1] = JETON[1];
  SAV_JETON[2] = JETON[2]; Aff_la_PILE(2); Index = 3;} // Index = 3 pour transmettre correctement le JETON.

void Valide_les_trois_postes_de_travail() {for (byte I = 0; I < 4; I++) Poste_disponible[I] = true;}

void Boucle_INFINIE() { // Génération automatique des JETONs aléatoires.
  Temps_pour_tester_le_dialogue = millis(); // Démarre le chronométrage pour vérifier la communication.
  while (true) { // Pour boucler de façon infinie. 
     digitalWrite(LED_triple_ROUGE,LOW); // Annule une éventuelle erreur précédente.
     //---------------- Vérification du dialogue toutes les minutes ----------------
     if (millis() - Temps_pour_tester_le_dialogue > 60000)  {
        Temps_pour_tester_le_dialogue = millis(); Tester_le_dialogue();}
     //---------------------------- Tester le BP sur A1 ----------------------------
  if (analogRead(BP_sur_A1) < 512) {
     digitalWrite(LED_Arduino,HIGH);
     while (analogRead(BP_sur_A1) < 512); // Attendre le relâcher.
     Noir = !Noir; if (Noir) Eteindre_les_Ecrans();
                   else {Ecran_OLED_en_exploitation(); Active_les_Ecrans_poste_de_Travail();}
     Active_les_indisponibilites(); // rétablit l'état inversé par l'instruction ci-dessus.
     digitalWrite(LED_Arduino,LOW);}
     //-----------------------------------------------------------------------------
   Un_sur_deux = !Un_sur_deux;
   if (Un_sur_deux && (Nb_Valides > 0)) {Genere_un_JETON_aleatoire_pour_un_poste_valide();
       TX_JETON_et_Aff_orphelin_sur_OLED();}
   else {if (Nb_dans_la_PILE > 0) {
            Depiler_un_JETON(); TX_JETON_et_Aff_orphelin_sur_OLED();}}}}

void Mode_automatique() {
  //---------------------------- Préparations initiales ----------------------------
  Compteur = 0;  INFINI = true; Valide_les_trois_postes_de_travail();
  Serial.println(); Serial.println(F(" Mode INFINI : Sortie par RESET."));
  // Ci-dessous : Booléen INFINI : Pour ne plus que le relais d'isolement batte à chaque JETON.
  digitalWrite(Relais,LOW); // Rétablit TX,RX du HC-05 d'entrée de façon statique.
  digitalWrite(LED_Arduino,LOW); Silence = true; Attente_de_la_synchronisation();
  Initialise_le_Registre_ATTENTE(); Nb_dans_la_PILE = 0; Nb_Valides = Nb_postes_de_Travail; Aff_la_PILE(0);
  Serial.println(); Active_les_indisponibilites();
  //--------------------- Génère les JETONS en boucle infinie ----------------------
  Boucle_INFINIE();}

//=========================== Corps du programme ===============================

void setup() {
  Initialise_les_sorties(); HC05_de_Sortie.begin(9600);
  u8g.setFont(u8g_font_6x10); // Police de matrices de 6x10 pixels lettres MJA/min comprises.
  while(HC05_de_Sortie.available()) CAR_USB = HC05_de_Sortie.read(); // Vider le tampon.
  Compteur = 0; INFINI = false; Led_Arduino = true; Noir = false; Silence = false;
  Valide_les_trois_postes_de_travail(); if (!Developpement) Page_de_presentation();
  //---------------------------- Tester le BP sur A1 ----------------------------
  Nb_postes_de_Travail = 2;
  if (analogRead(BP_sur_A1) < 512) {
     Nb_postes_de_Travail = 3; Un_sur_deux = true;
     digitalWrite(LED_Arduino,HIGH); Aff_Att_SYNCHRO_sur_OLED();
     while (analogRead(BP_sur_A1) < 512); // Attendre le relâcher.
     digitalWrite(LED_Arduino,LOW);
     Affichage_du_chrono_pour_tester_A3();}
  //-----------------------------------------------------------------------------
  Serial.begin(9600); while (Serial.available()) CAR_USB = Serial.read(); // Vider le tampon série.
  Presenter_le_PGM_sur_USB(); Initialise_le_Registre_ATTENTE(); Aff_NB_poste_de_travail();
  Nb_Valides = Nb_postes_de_Travail; Nb_dans_la_PILE = 0; 
  //---------------------------- Tester le BP sur A3 ----------------------------
  if (analogRead(BP_sur_A3) < 512) {
     digitalWrite(LED_Arduino,HIGH); Affiche_mode_INFINI_sur_OLED();
     while (analogRead(BP_sur_A3) < 512); // Attendre le relâcher.
     digitalWrite(LED_Arduino,LOW); Mode_automatique();} 
  //-----------------------------------------------------------------------------
  Temps_pour_tester_le_dialogue = millis();
  Attente_de_la_synchronisation(); Vider_le_tampon_HC05(); 
  Serial.println(); Serial.println(F(" >>>>>> Debut du dialogue. <<<<<<"));}

void loop() { // Attente sur Moniteur ou génération automatique en mode INFINI.
  if (Developpement) { Serial.println(); Serial.print(F(" Saisir un JETON > "));}
  Attendre_une_commande_sur_USB(); Affiche_JETON_sur_USB();
  //=============================================================================
  if (Index > 1) TX_JETON_et_Aff_orphelin_sur_OLED();
  else Traiter_la_Commande();}



