/* Programme INTERCALAIRE pour tester un réseau en alternat par Bluetooth HC-05 avec trois unités.
   Dans cette démonstration le but de l'intercalaire est de doubler la portée maximale entre l'unité
   Maître et l'unité Esclave. Si le système est en mode "Manuel" la LED d'Arduino clignote quand
   le programme est en attente d'une commande. En mode INFINI la LED reste éteinte.

BRANCHEMENTS à effectuer :
--------------------------
   Le module HC-05 d'entrée  : GND -> GND    VCC -> +5V ou +3,3V   TXD -> D0   RXD -> D1 avec R1:R2
   Le module HC-05 de sortie : GND -> GND    VCC -> +5V ou +3,3V   TXD -> D2   RXD -> D3 avec R1:R2

NOTE : Comme sur le module "UNIVERSEL" qui sert d'intercalaire 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 il suffit que l'un des couples ne communique plus pour que
       les alternats cessent et que l'on dépasse le TIME OUT. Cet aspect ne sera donc pris en compte que
       sur l'unité Maitre et sur l'unité Esclave.
   
Compilateur v1.7.9.             PGM               RAM dynamique
Tailles en DEVELOPPEMENT : 15266 Octets (49%)    1370 Octets (66%)
Tailles en UTILISATION   : 15038 Octets (48%)    1370 Octets (66%)  */

//------------------------ Constantes du programme --------------------------
#define Version "07/09/2025." // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Taille_PGM 15038 // Taille du programme. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define RAM_Dynamique 1370 // Taille de la RAM dynamique @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Pour_cent_PGM 48 // Pourcentage du programme. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Pour_cent_DYN 66 // Pourcentage des données. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Taille_PGM_Developpement 15266 // Taille du programme. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define RAM_Dynamique_Developpement 1370 // Taille de la RAM dynamique @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Pour_cent_PGM_Developpement 49 // Pourcentage du programme. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Pour_cent_DYN_Developpement 66 // Pourcentage des données. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Lgr_TEXTE 18 // @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
#define Developpement true // True pour Développement / false pour ensemble complet. @@@@@@@@@@@@@@@@@@@@@@
#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 -----------------------
// Les broches choisies sont séparées pour les écarter et faciliter les branchements provisoires.
#define RXE 0 // Va sur TXD du module Bluetooth d'Entrée.
#define TXE 1 // Va sur RXD du module Bluetooth d'Entrée avec pont de résistances.
#define RXS 2 // Va sur TXD du module Bluetooth de Sortie.
#define TXS 3 // Va sur RXD du module Bluetooth de Sortie avec pont de résistances.
#define LED_Arduino 13

//---------------------- 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(RXS,TXS);
#include <U8g2lib.h>   // L'afficheur est sur A4 et A5 pour SDA et SCL.
#include <SPI.h>
#include <Wire.h>
U8G2_SSD1306_128X32_UNIVISION_F_HW_I2C u8g2(U8G2_R0);

//------------------------- Variables du programme --------------------------
boolean Encadrement, INFINI, Noir;
byte Index;
char CAR_sur_RX, SAV_CAR;
char TEXTE[Lgr_TEXTE];
unsigned long Temps_Ecoule_depuis_RESET = 0;

//========================= 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 Saisie_Consigne_en_ENTREE() {
   while (!Serial.available()) { // Attendre l'arrivée d'un TEXTE.
           //================= Chronométrage pour le délai écoulé ================
           if (millis() - Temps_Ecoule_depuis_RESET > 75)  {
           Temps_Ecoule_depuis_RESET = millis();
           if (!INFINI) {Inverser_LED_Arduino(); delay(100);} else digitalWrite(LED_Arduino,LOW);}}
           //=====================================================================
   while(Serial.available()) {CAR_sur_RX = Serial.read();
         if ((Index < Lgr_TEXTE) && (CAR_sur_RX != CR) && (CAR_sur_RX != LF)) {
            //------------ Passer les lettre en MAJUSCULES -----------
            if ((CAR_sur_RX >= 'a') && (CAR_sur_RX < char(123))) CAR_sur_RX = CAR_sur_RX & 223;
            // Bien moins couteux en octets que Chaine_Memorisee.toUpperCase().
            //---------- Substitue des signes en MAJUSCULES ----------
            if ((CAR_sur_RX == '?') || (CAR_sur_RX == ',')) CAR_sur_RX = 'H';
            //--------------------------------------------------------
            TEXTE[Index] = CAR_sur_RX; Index++;}}}

void Attendre_un_TEXTE_sur_Bluetooth() {
  Index = 0;
  while (!Module_HC05_en_SORTIE.available()); // Attendre l'arrivée d'un TEXTE.
        while(Module_HC05_en_SORTIE.available()) {CAR_sur_RX = Module_HC05_en_SORTIE.read();
        if ((Index < Lgr_TEXTE) && (CAR_sur_RX != CR) && (CAR_sur_RX != LF)) {
           TEXTE[Index] = CAR_sur_RX; Index++;} delay(5);}} // Délai impératif

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

void Affiche_Intercalaire_sur_OLED() {
  u8g2.clearBuffer(); // Efface la mémoire interne.
  u8g2.setFont(u8g2_font_shylock_nbp_tf); // Choisir la police de caractère affichée.
  u8g2.drawFrame(2,0,126,32); u8g2.drawFrame(4,2,122,28);
  u8g2.drawStr(21,20," INTERCALAIRE.");
  u8g2.sendBuffer();}

void Affiche_APPAIRAGE_sur_OLED() {
  u8g2.clearBuffer(); // Efface la mémoire interne.
  u8g2.drawFrame(2,0,126,32);
  u8g2.setFont(u8g2_font_shylock_nbp_tf); // Choisir la police de caractère affichée.
  u8g2.drawStr(20,14,"Att. APPAIRAGE"); u8g2.drawStr(14,28,"des BLUETOOTHs.");
  u8g2.sendBuffer();} // Transférer le texte sur l'afficheur.

void Affiche_la_commande_sur_OLED(char Commande) {
  if (Index == 1) {
     if (Commande == 'N') Noir = !Noir; if (Commande == 'E') Encadrement = !Encadrement;}
  u8g2.clearBuffer(); // Efface la mémoire interne.
  if (!Noir) {
     if (Encadrement) u8g2.drawFrame(2,0,126,32);
     u8g2.setFont(u8g2_font_shylock_nbp_tf); // Choisir la police de caractère affichée.
     u8g2.drawStr(9,14," Commande : "); u8g2.setCursor(88,14);
     if (Index == 1) u8g2.print(Commande); else u8g2.print(TEXTE);}
  u8g2.sendBuffer();} // Transférer le texte sur l'afficheur.

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 Affiche_ACR() {
  if (Developpement) Serial.print(F(" ACR num "));
     for (byte I = 0; I < Index; I++) Serial.print(TEXTE[I]); Serial.println();
  // Affiche l'ACR sur la ligne du bas de l'écran OLED.
  if (!Noir) {u8g2.setCursor(12,28);
     switch (SAV_CAR) {
       case 'B' : {u8g2.print(F("BONJOUR !")); break;}
       case 'C' : {u8g2.print(F("Compilateur 1.7.9.")); break;}
       case 'K' : {u8g2.print(F("PGM = ")); if (Developpement) u8g2.print(Taille_PGM_Developpement);
                   else u8g2.print(Taille_PGM); break;}
       case 'M' : {u8g2.print(F("Pile - Tas = ")); u8g2.print(SRAM_LIBRE()); break;}
       case 'Q' : {u8g2.print(F("Au REVOIR !")); break;}
       case 'P' : {u8g2.print(F("PGM ")); if (Developpement) u8g2.print(Pour_cent_PGM_Developpement);
                  else u8g2.print(Pour_cent_PGM); u8g2.print(F("% Dyn "));
                  if (Developpement) u8g2.print(Pour_cent_DYN_Developpement);
                  else u8g2.print(Pour_cent_DYN); u8g2.print('%'); break;}
       case 'R' : {u8g2.print(F("RESET options.")); break;}
       case 'U' : {u8g2.print(F("Dyn. = ")); if (Developpement) u8g2.print(RAM_Dynamique_Developpement);
                   else u8g2.print(RAM_Dynamique); break;}
       case 'V' : {u8g2.print(F("Rev : ")); u8g2.print(Version); break;}
     default : for (byte I = 0; I < Index; I++) u8g2.print(TEXTE[I]);}
     u8g2.sendBuffer();}}

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

void setup() {
  Serial.begin(9600); Serial.println(); Serial.print(F(" INTERCALAIRE : Version du "));
  Serial.println(Version); Serial.println(); pinMode(LED_Arduino, OUTPUT);
  //------------- Initialisation de la voie série complémentaire -------------
  pinMode(RXS,INPUT); pinMode(TXS,OUTPUT); Module_HC05_en_SORTIE.begin(9600);
  Vider_les_deux_tampons();
  //-------------- Attente confirmation des dialogues Bluetooth --------------
  u8g2.begin(); // Init OLED.
  //--------------------------------------------------------------------------
  Serial.println(F(" Passe en reception des commandes.")); Serial.println();
  INFINI = false; Encadrement = true; Noir = false; Affiche_Intercalaire_sur_OLED();}

void loop() {// ENTREE : Coté Maître.  SORTIE : Coté Esclave.
  Index = 0; Vider_les_deux_tampons();
  //------------------------- Attendre une consigne -------------------------
  Saisie_Consigne_en_ENTREE(); SAV_CAR = TEXTE[0]; // Instruction reçue sur D0 la ligne série d'Arduino.
  if (SAV_CAR == 'I') INFINI = true;
  //----------- Affichage local de la consigne arrivant du Maître -----------
  if (Developpement) {Serial.println(); Serial.print(F(" Consigne du Maitre : >"));
                      Liste_le_TEXTE_sur_USB(); Serial.print(F(" LGR = ")); Serial.println(Index);}                  
  //------------- Affiche la consigne sur la ligne OLED du haut -------------
  Affiche_la_commande_sur_OLED(TEXTE[0]); // Voir la note particulière en fin de listage.
  //--------------- Transmettre la consigne à l'unité Esclave ---------------
  for (byte I = 0; I < Index; I++) Module_HC05_en_SORTIE.print(TEXTE[I]);
       Module_HC05_en_SORTIE.write(CR); Module_HC05_en_SORTIE.write(LF);
  //------------- Attente de l'accusé de réception de l'Esclave -------------
  Attendre_un_TEXTE_sur_Bluetooth();
  //-------------- Afficher l'accusé de réception de l'Esclave --------------
  if (Index > 0) Affiche_ACR();} // Et transmet aussi en parallèle l'ACR vers le Maître.

// Le module Arduino élargi avec afficheur OLED installé présente un défaut. En effet, les deux
// colonnes de PIXELs situées à gauche ne s'allument pas. Pour pouvoir conduire le développement
// le module est utilisé en "mode dégradé" c'est à dire que la définition est considérée comme
// étant de 32 x 126 et tous les affichages sont cadrés dans ce format. Ainsi le programme
// présente correctement les données et les afficheurs corrects ne seront pas influencés. Donc ne pas
// s'étonner des cadrages "décalés" de deux colonnes à droite.


