/* Programme directement issu de P21_Poste_de_Travail_3. La seule différence réside dans 
   des actions qui sont spécifique. Il reste possible de tester ce démonstrateur en "aveugle" comme
   explicité dans le didacticiel pour P19-Bis_Poste_de_Travail_1.
   • 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.
   • Pendant la durée d'indisponibilité, en mode développement les quatre LEDs vertes s'illuminent.
   • Disposant sur l'afficheur OLED d'assez de hauteur pour présenter une quatrième ligne de texte,
   • on précise la valeur du compteur "Un_sur_N".
   Logiciel relatif au Poste de travail pour tester un réseau en anneaux par Bluetooth HC-05 avec quatre
   unités. Trois 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".
   Le démonstrateur est prévu pour une carte NANO STANDARD ou UNO avec éventuellement un afficheur OLED
   de 1,3'. La LED d'Arduino clignote tant qu'un premier jeton 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°3 câblé provisoirement sur un bac de test
           disposant de huit témoins logiques dont cinq seront utilisés.

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.
   D5 est relié à l'un des témoins logiques vert du banc de test.
   D6 est relié à l'un des témoins logiques vert du banc de test.
   D7 est relié à l'un des témoins logiques vert du banc de test.
   D8 est relié à l'un des témoins logiques vert du banc de test.
   D9 est relié à l'un des témoins logiques bleu du banc de test.
   
NOTE : Comme sur le module "UNIVERSEL" il n'y a pas de liaison avec les sorties, STATE (Autre que le
       pilotage des LEDs oranges.) 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 poste de
       pilotage en mode INFINI 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 trente secondes il réitère le test avec JETON "999".

Compilateur v1.7.9.              PGM               RAM dynamique   
Developpement               13710 Octets (44%)    656 Octets (32%)
Utilisation                 13110 Octets (42%)    656 Octets (32%) */

//------------------------ Constantes du programme --------------------------
#define Version "08/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 'K' // 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_V1 5
#define LED_V2 6
#define LED_V3 7
#define LED_V4 8
#define LED_B1 9
#define LED_Arduino 13
//------------------- Définition des broches analogiques --------------------
// 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 "U8glib.h"
U8GLIB_SH1106_128X64 u8g(U8G_I2C_OPT_NONE); // Usage de la ligne I2C (Two Wire Interface)

//------------------------- Variables du programme --------------------------
boolean Clignote_si_JETON_non_acquis, Encadrement, Eteindre_la_puce, Ne_TX_pas_le_JETON, Noir,
        Occupe_possible, Un_sur_N_en_cours;
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 Affiche_ATTENTE_sur_OLED() {
  u8g.firstPage(); do {// Début de la boucle d'affichage de la page écran.
    u8g.drawFrame(2,0,126,32);
    u8g.drawStr(10,13,"Version"); u8g.drawStr(64,13,Version);u8g.drawStr(10,27,"Attend  un JETON.");
    u8g.drawStr(10,44,"Poste de Travail 3");}
  while(u8g.nextPage());} // 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 au lieu de "///".
  if (Developpement) {Serial.print(F(" JETON en sortie : ")); Liste_le_contenu_de_JETON();}}

void Ecran_Dialogue() {
  u8g.firstPage(); do {// Début de la boucle d'affichage de la page écran.
    if (!Noir) {
       if (Un_sur_N == MAX_chrono) if (!Eteindre_la_puce) u8g.drawDisc(10,23,3);
       if (Encadrement) u8g.drawFrame(2,0,126,32); u8g.drawFrame(109,46,11,11);
       u8g.drawStr(18,13,"JETON  TX1 = "); u8g.setPrintPos(98,14); u8g.print(SAVJ0);
       u8g.print(SAVJ1); u8g.print(SAVJ2); u8g.setPrintPos(18,27); u8g.print("JETON RX2 = ");
       u8g.setPrintPos(98,27);
       if (Ne_TX_pas_le_JETON) {u8g.print('/'); u8g.print('/'); u8g.print('/');} 
       else {u8g.print(JETON[0]); u8g.print(JETON[1]); u8g.print(JETON[2]);}
       u8g.drawStr(10,43,"Poste de Travail 3");
       if (Un_sur_N_en_cours) {u8g.drawStr(10,55,"Reste "); u8g.setPrintPos(46,55);
          u8g.print(Reste_chrono); u8g.print('s');}
       else {u8g.drawStr(10,55,"Un sur N = "); u8g.setPrintPos(76,55); u8g.print(Un_sur_N);
             u8g.print('/'); u8g.print(MAX_chrono);}
       u8g.setPrintPos(112,55); if (Occupe_possible) u8g.print('O'); else u8g.print('N');}}
   while(u8g.nextPage());}

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); Un_sur_N_en_cours = true;
  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.
     //============ 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();
     Un_sur_N_en_cours = false; Eteindre_la_puce = true; Ecran_Dialogue(); Eteindre_la_puce = false;
     Reste_chrono = 0; Traiter_le_JETON();}

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] == '3') {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();}}

//================= Procédures spécifiques au poste de travail =================
void Eteindre_toutes_les_LEDs() {
  digitalWrite(LED_V1, LOW); digitalWrite(LED_V2, LOW); digitalWrite(LED_V3, LOW);
  digitalWrite(LED_V4, LOW); digitalWrite(LED_B1, LOW); digitalWrite(LED_Arduino,LOW);}

void Allume_une_LED(byte Num_LED) {
  Eteindre_toutes_les_LEDs();
  switch(Num_LED) {
    case 1 : {digitalWrite(LED_V1, HIGH); break;}
    case 2 : {digitalWrite(LED_V2, HIGH); break;}
    case 3 : {digitalWrite(LED_V3, HIGH); break;}
    case 4 : {digitalWrite(LED_V4, HIGH); break;}
    case 5 : {digitalWrite(LED_B1, HIGH); break;}
    case 6 : digitalWrite(LED_Arduino, HIGH);}}

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

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

void setup() {
  Serial.begin(9600); Serial.println(); Serial.print(F(" POSTE de travail num 3 : Version du "));
  Serial.println(Version); Serial.println(); Serial.println(F(" Attend un premier JETON."));
  Serial.println();
  pinMode(LED_Arduino, OUTPUT); pinMode(LED_V1, OUTPUT); pinMode(LED_V2, OUTPUT);
  pinMode(LED_V3, OUTPUT); pinMode(LED_V4, OUTPUT); pinMode(LED_B1, 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 -----------
  u8g.setFont(u8g_font_6x10); // Police de matrices de 6x10 pixels lettres MJA/min comprises.
  Encadrement = true; Noir = false; Clignote_si_JETON_non_acquis = true; Affiche_ATTENTE_sur_OLED();
  //-------------- Attente confirmation des dialogues Bluetooth --------------
  Eteindre_la_puce = false; Un_sur_N_en_cours = 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] == '3') && (JETON[2] == 'A')) Noir = true;
  //-------------------------- 1:B est prioritaire ---------------------------
  if ((JETON[0] == '3') && (JETON[2] == 'B')) Noir = false;
  //--------------------------------------------------------------------------
  if ((Occupe_possible) && (JETON[0] == '3')) 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.


