/* Clone de P23_Poste_de_Travail_1 qui utilise encore le petit afficheur bicolore OLED SSD1202.
   Programme relatif au Poste de travail pour tester un réseau en anneaux par Bluetooth HC-05 avec trois
   unités. Deux sont des Postes de Travail avec pour chacun des tâches spécifiques. Toutefois, les six
   premiers Travaux sont communs à toutes les "unités de fabrication".
   • La particularité de ce programme, c'est que le comportement du poste de Travail devient plus
   • réaliste. Aléatoirement une fois sur 3 à une fois sur six l'unité ne sera pas apte à traiter le
   • JETON la concernant pendant une durée allant aléatoirement de cinq à dix secondes. Durant cette
   • phase tout JETON capturé sera immédiatement renvoyé dans le réseau en orphelin.
   Le démonstrateur utilise une carte NANO standard avec afficheur bicolore OLED de 32 x "127" PIXELs.
   La LED d'Arduino clignote tant qu'un premier jeton concernant le poste n'a pas été réceptionné.
   Par défaut aucune LED n'est allumée, OLED est actif et l'encadrement est affiché.
   >>>>>>> Ce démonstrateur concerne le poste de travail n°1 d'adresse 1 utilisant le circuit de
           de développement UNIVERSEL qui ne dispose que de la LED triple et la Rouge de 3mm de diamètre.
           Naturellement il faudra enlever le Strap et réunir la LED sur D9 par exemple qui est disponible
           le pilotage se faisant en logique négative.

   • Si Developpement = true, toute saisie qu'elle soit au clavier ou du Bluetooth engendrera des
     affichages sur le Moniteur de l'IDE. par ailleurs, tant que le cronomètre rend inapte l'unité
     de travail, la LED triple verte s'illumine en violet.

     ********************** À modifier sur chaque nouveau poste de travail *********************
     *    • Mettre à jour #define Nb_Actions_MAX 'J'.                                          *
     *    • La procédure Recevoir_un_JETON()() doit être adaptée sur if (JETON[0] == 'N').     *
     *    • Adapter la procédure Traite_le_TRAVAIL().                                          *
     *    • Adapter les textes dans la procédure Ecran_Dialogue().                             *
     *******************************************************************************************

BRANCHEMENTS à effectuer :
--------------------------
   En entrée : Esclave 1 : GND -> GND  VCC -> +5V ou +3,3V   TXD -> (RX) D0  (TX) D1 -> RXD avec R1/R2
   En sortie : Maître 2  : GND -> GND  VCC -> +5V ou +3,3V   TXD -> D2            D3 -> RXD avec R1/R2
   ATTENTION : A4 et A5 sont réservés pour la gestion de l'écran OLED.
   D6 est relié à la LED tricolore Rouge.
   D7 est relié à la LED tricolore Verte.
   D8 est relié à la LED tricolore Bleue.
   D9 est branché sur la LED rouge de 3mm de diamètre. (Strap enlevé")
   
NOTE : Comme sur le module "UNIVERSEL" il n'y a pas de liaison avec les sorties, STATE (Autre que le
       pilotage des LEDs vertes.) il n'est pas possible de tester l'accouplement entre
       les modules. Ce n'est pas impératif, car pour tester le fonctionnement du réseau le Maître se
       contente de transmettre un JETON adressé "à personne" et attend en retour un JETON orphelin. Quand
       il le reçoit c'est que le réseau fonctionne. Pour en vérifier l'activité, toutes les minutes
       il réitère le test avec JETON "anonyme".
   
Compilateur v1.7.9.              PGM               RAM dynamique
Developpement               12676 Octets (41%)    517 Octets (25%)
Utilisation                 11842 Octets (38%)    517 Octets (25%) */

//------------------------ Constantes du programme --------------------------
#define Version "09/11/25." // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Developpement true // Utilise le moniteur pour envoyer les JETONS. (HC-05 en entrée enlevé @@@@@@@@@
#define Lgr_TEXTE 5 // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Nb_Actions_MAX 'J' // Nombre maximum d'actions possibles sur ce poste @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#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 RX 0 // Va sur TXD du module Bluetooth d'Entrée mais pas exploité.
#define TX 1 // Va sur RXD du module Bluetooth d'Entrée avec pont de résistances.
#define TXD 2 // Va sur TXD du module Bluetooth de Sortie mais n'est pas exploité.
#define RXD 3 // Va sur RXD du module Bluetooth de Sortie avec pont de résistances.
#define LED_triple_ROUGE 6
#define LED_triple_VERTE 7
#define LED_triple_BLEUE 8
#define LED_rouge_3mm 9
#define broche_SDA 11 // D11 broches pour la ligne I2C.
#define broche_SCL 12 // D12 broches pour la ligne I2C.
#define LED_Arduino 13
//------------------- Définition des broches analoliques --------------------
// Néant.

//---------------------- 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 Module_HC05_en_SORTIE(TXD,RXD);
#include <Adafruit_ssd1306syp.h>
// Initialisation des broches en sorties et affectations.
Adafruit_ssd1306syp display(broche_SDA, broche_SCL);

//------------------------- Variables du programme --------------------------
boolean Clignote_si_JETON_non_acquis, Encadrement, Eteindre_la_puce, Ne_TX_pas_le_JETON,
        Noir, Occupe_possible, Reste_chrhono;
char CAR_USB, Erreur, SAVJ0, SAVJ1, SAVJ2;
byte Duree_aleatoire, Index, MAX_chrono, Reste_chrono, Un_sur_N;
char JETON[Lgr_TEXTE];
unsigned long Clignotement = 0;
unsigned long Chrono_DUREE;
//========================= Procédures de servitude ============================
void Inverser_LED_Arduino() {digitalWrite(LED_Arduino,!digitalRead(LED_Arduino));}

void Vider_les_deux_tampons() {
  while(Serial.available()) Serial.read(); // Vider le tampon de RX.
  while(Module_HC05_en_SORTIE.available()) Module_HC05_en_SORTIE.read();} // Vider le tampon de RXS.

void CADRE() {if (Encadrement) display.drawRect(0,16,128,45,WHITE);}

void CADRE_UP() {if (Encadrement) display.drawRect(0, 0, 128, 16, WHITE);}

void Affiche_cadre_du_haut(byte X) {CADRE_UP(); display.setCursor(X,4);}

void Affiche_ATTENTE_sur_OLED() {
  display.clear(); Affiche_cadre_du_haut(9); display.print("Poste de Travail 1.");
  CADRE(); display.setCursor(16,24); display.print("Version "); display.print(Version);
  display.setCursor(16,41); display.print("Attend  un JETON.");
  display.update();} // Transférer le texte sur l'afficheur.

void Analyseur_syntaxique() {// Vérifie le texte présent dans JETON[].
  Erreur = '0';
  if (Index != 3) Erreur = '1';
  if ((JETON[2] < 'A') || (JETON [2]> Nb_Actions_MAX)) Erreur = '2';
  if (JETON[1] != ':') Erreur = '3';}

void Attendre_un_JETON_sur_USB() {// Donc arrivant du Moniteur ou du Bluetooth Esclave n°1.
  Index = 0;
  while (!Serial.available()) { // Attente d'une saisie sur le Moniteur.
        //================== Chronométrage pour JETON non trouvé ==================
        if (Clignote_si_JETON_non_acquis) {
           if (millis() - Clignotement > 75) {Clignotement = millis(); Inverser_LED_Arduino();}}}
        //-------------------------------------------------------------------------
  while (Serial.available()) {CAR_USB = Serial.read();
         if ((Index < Lgr_TEXTE) && (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.
  SAVJ0 = JETON[0]; SAVJ1 = JETON[1]; SAVJ2 = JETON[2];}

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

void Transmet_le_JETON_en_sortie() {
  for (byte I = 0; I < Index; I++) Module_HC05_en_SORTIE.print(JETON[I]);
       Module_HC05_en_SORTIE.write(CR); Module_HC05_en_SORTIE.write(LF);
       Ne_TX_pas_le_JETON = false; // Pour afficher le JETON sur OLED au lieu de "///".
  if (Developpement) {Serial.print(F(" JETON en sortie : ")); Liste_le_contenu_de_JETON();}}

void Ecran_Dialogue() {
  display.clear();
  if (!Noir) {CADRE();
     if (Un_sur_N == MAX_chrono) if (!Eteindre_la_puce)display.drawCircle(9,44,3,WHITE);
     Affiche_cadre_du_haut(9); display.print("Poste de Travail 1.");
     display.setCursor(8,24); if (Occupe_possible) display.print('O'); else display.print('N');
     display.setCursor(16,24); display.print("JETON / TX1 = ");
     display.print(SAVJ0); display.print(SAVJ1); display.print(SAVJ2);
     display.setCursor(16,41); display.print("JETON / RX2 = ");
     if (Ne_TX_pas_le_JETON) display.print("///"); 
     else {display.print(JETON[0]); display.print(JETON[1]); display.print(JETON[2]);}}
  display.update();} // Transférer le texte sur l'afficheur.

void Aff_a_partir_d_ici() {Serial.print(char(224)); Serial.println(F(" partir d'ici."));}

void Aff_occupe() {Serial.println(); Serial.print(F(" Occup")); Serial.print(char(233));}

void Traiter_le_JETON() {
  if (Developpement) {Serial.println(); Serial.print(F(" JETON re")); Serial.print(char(231));
     Serial.print(F("u : ")); Liste_le_contenu_de_JETON();}
  //------------------------------------------------------------------------------
  if (JETON[0] == '1') {Analyseur_syntaxique();
     if (Erreur > '0') {JETON[1] = Erreur; JETON[2] = 'E'; Transmet_le_JETON_en_sortie(); Ecran_Dialogue();}
     else {digitalWrite(LED_Arduino, LOW); Traite_le_TRAVAIL(); Ne_TX_pas_le_JETON = true;
           Ecran_Dialogue();}}
  else {Transmet_le_JETON_en_sortie(); Ecran_Dialogue();}}

void Determine_Un_sur_N() {Un_sur_N = random(3,7); MAX_chrono = Un_sur_N; Un_sur_N = 0;}

void Traiter_un_sur_N() {
  Duree_aleatoire = random(25,41);
  if (Developpement) {
     Serial.println(); Serial.println(F(" Traite 1/N.")); Serial.print(F(" Dur")); Serial.print(char(233));
     Serial.print(F("e al")); Serial.print(char(233)); Serial.print(F("toire = "));
     Serial.print(Duree_aleatoire); Serial.println(F(" secondes."));}
     Ecran_Dialogue();
     Chrono_DUREE = millis(); // Arme le cronomètre.
     if (Developpement) {digitalWrite(LED_triple_BLEUE, HIGH); digitalWrite(LED_triple_ROUGE, HIGH);}
     //============ Chronométrage pour la durée d'indisponibilité =============
     Reste_chrono = Duree_aleatoire;
     while (Reste_chrono > 0) {
         Reste_chrono = Duree_aleatoire - ((millis() - Chrono_DUREE) / 1000);
        if (Reste_chrono > 36) Reste_chrono = 0;
        if (Developpement) {Serial.println(); Serial.print(F(" Reste "));
        Serial.print(Reste_chrono); Serial.print('s');} Ecran_Dialogue(); 
        Transmet_le_JETON_en_sortie(); Attendre_un_JETON_sur_USB(); Ecran_Dialogue();}
     if (Developpement) Eteindre_toutes_les_LEDs(); Determine_Un_sur_N();
     Eteindre_la_puce = true; Ecran_Dialogue(); Eteindre_la_puce = false;
     Reste_chrono = 0; Traiter_le_JETON();}

//================= Procédures spécifiques au poste de travail =================
void Eteindre_toutes_les_LEDs() {
  digitalWrite(LED_triple_ROUGE, LOW); digitalWrite(LED_triple_VERTE, LOW);
  digitalWrite(LED_triple_BLEUE, LOW); digitalWrite(LED_rouge_3mm, HIGH); digitalWrite(LED_Arduino, LOW);}

void Allume_une_LED(byte Num_LED) {
  Eteindre_toutes_les_LEDs();
  switch(Num_LED) {
    case 1 : {digitalWrite(LED_triple_ROUGE, HIGH); break;}
    case 2 : {digitalWrite(LED_triple_VERTE, HIGH); break;}
    case 3 : {digitalWrite(LED_triple_BLEUE, HIGH); break;}
    case 4 : {digitalWrite(LED_rouge_3mm, LOW); break;}
    case 13 : digitalWrite(LED_Arduino, HIGH);}}

void Traite_le_TRAVAIL() {
  switch (JETON[2]) {
     case 'C' : {Allume_une_LED(13); break;}
     case 'D' : {Eteindre_toutes_les_LEDs(); break;}
     case 'E' : {Encadrement = true; break;}
     case 'F' : {Encadrement = false; break;}
     case 'G' : {Allume_une_LED(4); break;}
     case 'H' : {Allume_une_LED(3); break;}
     case 'I' : {Allume_une_LED(2); break;}
     case 'J' : Allume_une_LED(1);}}

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

void setup() {
  Serial.begin(9600); Serial.println(); Serial.print(F(" POSTE de travail num 1 : Version du "));
  Serial.println(Version); Serial.print(F(" PGM avec indisponibilit")); Serial.print(char(233));
  Serial.print(F("s al")); Serial.print(char(233)); Serial.print(F("atoires.")); Serial.println();  
  Serial.println(); Serial.println(F(" Attend un premier JETON.")); Serial.println();
  pinMode(LED_Arduino, OUTPUT); pinMode(LED_triple_ROUGE, OUTPUT); pinMode(LED_triple_VERTE, OUTPUT);
  pinMode(LED_triple_BLEUE, OUTPUT); pinMode(LED_rouge_3mm, OUTPUT); Eteindre_toutes_les_LEDs();
  //------------- Initialisation de la voie série complémentaire -------------
  pinMode(RXD,OUTPUT); pinMode(TXD,INPUT); Module_HC05_en_SORTIE.begin(9600);
  //----------- Affichage OLED avant synchronisation des Bluetooth -----------
  display.initialize(); delay(100); display.setTextSize(1); // Taille la plus petite.
  Encadrement = true; Noir = false; Clignote_si_JETON_non_acquis = true; Affiche_ATTENTE_sur_OLED();
  //-------------- Attente confirmation des dialogues Bluetooth --------------
  Eteindre_la_puce = false; Vider_les_deux_tampons(); Attendre_un_JETON_sur_USB();
  Occupe_possible = false; Un_sur_N = 0; ; MAX_chrono = 99;
  Traiter_le_JETON(); Clignote_si_JETON_non_acquis = false; digitalWrite(LED_Arduino, LOW);}

void loop() {
  if (Developpement) {Serial.println(); Serial.print(F(" 1/N = ")); Serial.print(Un_sur_N);
                      Serial.print('/'); Serial.print(MAX_chrono);}
  Attendre_un_JETON_sur_USB(); // Donc arrivant du Moniteur ou du Bluetooth Esclave n°1.
  //-------------------------- DEB est prioritaire ---------------------------
  if ((JETON[0] == 'D') && (JETON[1] == 'E') && (JETON[2] == 'B')) {
     Occupe_possible = true; Determine_Un_sur_N();
     if (Developpement) {Aff_occupe(); Serial.print(F(" Actif ")); Aff_a_partir_d_ici();}}
  //-------------------------- FIN est prioritaire ---------------------------
  if ((JETON[0] == 'F') && (JETON[1] == 'I') && (JETON[2] == 'N')) {
     Occupe_possible = false; Un_sur_N = 0; MAX_chrono = 99; 
     if (Developpement) {Aff_occupe(); Serial.print(F(" Impossible ")); Aff_a_partir_d_ici();}}
  //-------------------------- 1:A est prioritaire ---------------------------
  if ((JETON[0] == '1') && (JETON[2] == 'A')) Noir = true;
  //-------------------------- 1:B est prioritaire ---------------------------
  if ((JETON[0] == '1') && (JETON[2] == 'B')) Noir = false;
  //--------------------------------------------------------------------------
  if ((Occupe_possible) && (JETON[0] == '1')) Un_sur_N++;
  if (Un_sur_N == MAX_chrono) Traiter_un_sur_N();
  else Traiter_le_JETON();} // Si poste concerné analyse le contenu et génère une erreur si non valide.


