//
//  ███████╗ ██╗  ██╗ ███████╗ ██╗      ██╗           ██████╗ ███    ███╗ ██████╗  ██╗      ██╗ ███    ██╗ ███████╗ 
//  ██═════╝ ██║  ██║ ██╔════╝ ██║      ██║          ██╔════╝ ████  ████║ ██╔══██╗ ██║      ██║ ████   ██║ ██╔════╝ 
//  ███████╗ ███████║ █████╗   ██║      ██║          ██║      ██║████╔██║ ██║  ██║ ██║      ██║ ██║██  ██║ █████╗   
//       ██║ ██╔══██║ ██╔══╝   ██║      ██║          ██║      ██║ ██╔╝██║ ██║  ██║ ██║      ██║ ██║ ██ ██║ ██╔══╝   
//  ███████║ ██║  ██║ ███████╗ ███████╗ ███████╗     ╚██████╗ ██║ ╚═╝ ██║ ██████╔╝ ███████╗ ██║ ██║  ████║ ███████╗  
//  ╚══════╝ ╚═╝  ╚═╝ ╚══════╝ ╚══════╝ ╚══════╝      ╚═════╝ ╚═╝     ╚═╝ ╚═════╝  ╚══════╝ ╚═╝ ╚═╝  ╚═══╝ ╚══════╝  
// 
/*************************************************************************************************************************************************************
* Serial Commande Line CLI : cmd Serial / Telnet -- Version UNO R4 Minima + Shield Ethernet2 (A000024 W5500) --- Lib ethernet.h
*
* Gestion de la carte MicroSD sur la carte reseau Ethernet2
* Original lib // See https://github.com/IndustrialShields/arduino-cmdLine  Attention elle a ete fortement modifiee !!
**************************************************************************************************************************************************************
* Declarer des fonctions de commandes (necessaires avant de definir des commandes). 
* Ces fonctions sont executees lorsque l'utilisateur tape la commande associee a l'argument.
* Fonctionnement :
* 
*   Serial
*   Telnet      ------> [ Lib CmdLineTelnet_UNOR4.h/cpp ]  ----->  Execute le sous programme <command> dans le SHELL
*   Bluetooth(ESP32)     Interprete : <command> <arg>            void <commande>(const char *<arg>) { ..... Fonction a executer .....  }  
*                           un espace entre les deux              void <commande sans arg>(const char *arg) { (void)arg  { ..... Fonction a executer .....  }
*
*   On peut appeler les fonction du shell dans tous le programme   ResetNow(nullptr);    // nullptr sans argument
* 
*   Le parametre arg renvoi une chaine de caractere
*   Interpretation d'une chaine de caracteres    : if ( strcmp (arg, "ON") == 0 || strcmp (arg, "on") == 0 )
*   Interpretation d'une adresse IP              :   IPAddress ip;
*                                                    ip.fromString(arg);
*                                                    for (int i = 0; i < 4; ++i) {      conf.ip[i] = ip[i];    }
**************************************************************************************************************************************************************
*							
*                    ┌──────────────────────────────────────────────┐                 ┌──────────────────────────────────────────────┐
*                    │  Libraire : CmdLineTelnetAuto_UNOR4          │                 │  Shell_CmdLine_SD.h                          │
*                    ├──────────────────────────────────────────────┤                 ├──────────────────────────────────────────────┤
*  Serial     <----->│  CmdLineTelnet_UNOR4.h / cpp                 │---------------->│  help · print · reset · mem · save · reload  │
*  (Putty)           │                                              │                 │  ip · gateway · msr · mac                    │
*                    │  Historique des commandes ↑ ↓                │                 │  dir · del · copie · rename · create         │
*  Ethernet   <----->│  Complétion TAB                              │                 │  ping · tracert · netstat                    │
*                    │                                              │                 │  led on / led off                            │
*                    │                                              │                 │                                              │
*  Bluetooth  <----->│  Envoi des messages print vers Serial Eth    │<----------------│  cmdline.print( );                           │
*  (ESP32)           └──────────────────────────────────────────────┘                 └──────────────────────────────────────────────┘
*
**************************************************************************************************************************************************************/
// Declaration des commandes SHELL disponibles :
void LoginCmdTelnetConnexion(const char *arg);
void SetCmdTelnetPass(const char *arg);
void Exit(const char *arg);
void PrintConf(const char *arg);

void Name(const char *arg);

void SetSrvWEB(const char *arg);
void SetSrvFTP(const char *arg);
void RamFree(const char *arg);

void SetPing(const char *arg);
void Netstat(const char *arg);
void Tracert(const char *arg);

void SetDatalogger(const char *arg);
void Create(const char *arg);
void Edit(const char *arg);
void Select(const char *arg);

void Ddir(const char *arg);
void RenameFichier(const char *arg);
void Del(const char *arg);
void Type(const char *arg);
void CopyFichier(const char *arg);
void FormatSD(const char *arg);                                           // Non implemente dans cette version

void LoginWEB(const char *arg);
void LoginPasswordWEB(const char *arg);
void LoginFTP(const char *arg);
void PasswordFTP(const char *arg);

void SetMac(const char *arg);
void SetDhcp(const char *arg);
void SetIp(const char *arg);
void Setsubnet(const char *arg);
void Setgateway(const char *arg);
void SetPort(const char *arg);


void SaveConfEEPROM(const char *arg);
void SetDefaultConf(const char *arg);
void LoadConfEEPROM(const char *arg);
void SaveConf(const char *arg);
void LoadConf(const char *arg);
void ResetNow(const char *arg);
void CLIecho(const char *arg);

void Version(const char *arg);

// Tableau des commandes systeme IoToS — passe a cmdLine.begin() dans IoToS_Boot_Driver.h
const cmd_t commands[] = {
  {"save",         SaveConfEEPROM},
  {"reset",        ResetNow},        {"restart",      ResetNow},     {"reboot",       ResetNow},

  {"login",        LoginCmdTelnetConnexion},
  {"password",     SetCmdTelnetPass},
  {"exit",         Exit},

  {"name",         Name},

  {"WEB",          SetSrvWEB},
  {"FTP",          SetSrvFTP},
  {"RAM",          RamFree},

  {"ping",         SetPing},
  {"netstat",      Netstat},
  {"tracert",      Tracert},

  {"datalogger",   SetDatalogger},
  {"create",       Create},
  {"select",       Select},

  {"dir",          Ddir},            {"ls",           Ddir},
  {"rename",       RenameFichier},
  {"del",          Del},
  {"edit",         Edit},
  {"type",         Type},
  {"copy",         CopyFichier},     {"copie",        CopyFichier},

  {"mac",          SetMac},
  {"dhcp",         SetDhcp},
  {"ip",           SetIp},           {"IP",           SetIp},
  {"subnet",       Setsubnet},
  {"gateway",      Setgateway},      {"passerelle",   Setgateway},   {"pass", Setgateway},
  {"port",         SetPort},

  {"reload",       LoadConfEEPROM},
  {"defaults",     SetDefaultConf},
  {"SaveConf",     SaveConf},
  {"LoadConf",     LoadConf},
  {"CLI",          CLIecho},

  {"loginFTP",     LoginFTP},
  {"passwordFTP",  PasswordFTP},

  {"loginWEB",     LoginWEB},
  {"login-passWEB",LoginPasswordWEB},

  {"version",      Version},
};

/*************************************************************************************************************
* Sous Programme : PrintConf
*=============================================================================================================*
* Fonction   : Affiche la configuration complete du systeme (reseau, services, EEPROM, RAM, heure NTP)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg
*              - nullptr    : affichage partiel (configuration Ethernet active uniquement)
*              - != nullptr : affichage complet (RAM + EEPROM)
* Sortie     : cmdLine.println -- affichage dans le terminal Serial ou Telnet
*-------------------------------------------------------------------------------------------------------------
* Variables lues (non modifiees) :
*   conf.nameSystem          -- nom du systeme
*   conf.login_cmdTelnetSwitch -- etat du verrou CLI : 0=ouvert / 1=verrouille
*   conf.led                 -- etat LED : 0=OFF / 1=ON
*   conf.dataloggerSwitch    -- etat datalogger : 0=OFF / 1=ON
*   conf.SrvWEBswitch        -- etat serveur WEB : 0=OFF / 1=ON
*   conf.SrvFTPswitch        -- etat serveur FTP : 0=OFF / 1=ON
*   conf.loginWEB            -- authentification WEB : 0=OFF / 1=ON
*   conf.dhcp                -- mode DHCP : 0=IP fixe / 1=DHCP
*   conf.ip / subnet / gateway / port / mac -- parametres reseau
*   microSD                  -- etat carte SD : 0=absente / 1=presente
*   etatWAN                  -- connexion Internet : 0=KO / 1=OK
*   dhcpEtat                 -- etat DHCP actif : 0=OFF / 1=ON
*   conf_TMP                 -- structure temporaire lue depuis l'EEPROM (si arg != nullptr)
*-------------------------------------------------------------------------------------------------------------
* Remarque   : Commandes declenchantes : print / ifconfig / ipconfig / info / infoip / config / conf / p
*************************************************************************************************************/
void PrintConf(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; } 

  cmdLine.println();
  cmdLine.println(F("\033[1;32;40m")); // Vert sur fond noir 
  cmdLine.println(F("###########################################################################")); 
  cmdLine.println(F("###  ---   SYSTEM   ---                        Serial Telnet Connexion  ###"));   
  cmdLine.println(F("###  Info Status Arduino UNO R4 - Ethernet2         Micro System IoToS  ###")); 
  cmdLine.println(F("###########################################################################  \033[0;39;49m"));   
  cmdLine.println();   
					
  PrintConf_user(nullptr);
  
  
  cmdLine.println(F("\033[1;31;40m"));  // Texte en brun 
  cmdLine.println(F(" Status Service"));                            cmdLine.println(F("\033[0;39;49m"));  // Texte normal
  cmdLine.print(  F("  - Nom du systeme. . . . . . . . . . . : ")); cmdLine.println(  conf.nameSystem); 
  cmdLine.print(  F("  - Etat Login cmdLine et Telnet. . . . : ")); cmdLine.println(  conf.login_cmdTelnetSwitch == 0 ? "OFF" : "ON"); 
  cmdLine.print(  F("    * Password cmdLine et Telnet        : ")); cmdLine.println(  conf.telnetPassword);
  cmdLine.println(); 
  cmdLine.print(  F("  - Carte Micro SD Partition Fichier. . : ")); cmdLine.println(  microSD == 0 ? "\033[;37;41m OFF \033[0;39;49m" : "ready");
  cmdLine.print(  F("  - Datalogger. . . . . . . . . . . . . : ")); cmdLine.println(  conf.dataloggerSwitch == 0 ? "OFF" : "ON");

  cmdLine.print(  F("    * Nom Fichier en cours              : ")); cmdLine.println(  conf.fichierEnCours); 
  cmdLine.println(F("  - Ethernet. . . . . . . . . . . . . . : ")); 
  cmdLine.print(  F("    * Test DNS                          : ")); cmdLine.println(  etatWAN == 0 ? "Pas de connexion Internet : Verifier le Reseau et la passerelle" : "Connexion a Internet OK");  
  cmdLine.print(  F("  - Serveur FTP . . . . . . . . . . . . : ")); cmdLine.println(  conf.SrvFTPswitch == 0 ? "OFF" : "ON");
  cmdLine.print(  F("    * Serveur FTP Login                 : ")); cmdLine.println(  conf.loginFTP);
  cmdLine.print(  F("    * Serveur FTP Password              : ")); cmdLine.println(  conf.passwordFTP); 
  cmdLine.print(  F("  - Serveur WEB . . . . . . . . . . . . : ")); cmdLine.println(  conf.SrvWEBswitch == 0 ? "OFF" : "ON"); 
  cmdLine.print(  F("    * Authentification                  : ")); cmdLine.print  (  conf.loginWEB ? "ON" : "OFF");              cmdLine.println( F("  (Login et Password) du site WEB"));
  cmdLine.print(  F("    * Login:Password                    : ")); cmdLine.print  (  conf.loginPasswordWEB);                     cmdLine.println( F("  en Base 64"));
					 

 
  // Info Reseau
  cmdLine.println(F("\033[1;31;40m"));  // Texte en brun   
  cmdLine.println(F(" Configuration en cours de la connexion Ethernet de l'Arduino UNO R4"));  cmdLine.println(F("\033[0;39;49m"));  // Texte normal
  IPAddress ip = Ethernet.localIP();
  IPAddress subnet =  Ethernet.subnetMask();
  IPAddress gateway = Ethernet.gatewayIP();
 
  cmdLine.print( F("  - DHCP. . . . . . . . . . . . . . . . : ")); cmdLine.println( dhcpEtat ? "ON" : "OFF");
  boolean testValidityIP = TestValidityIPadress(conf.ip, conf.subnet, conf.gateway);
  if ( !testValidityIP || conf.subnet[3] == 255 || conf.ip[3] == conf.gateway[3] || conf.ip[3] == 0 || conf.ip[3] == 255  )    
 {cmdLine.println(F("\033[;37;41m     ###     ATTENTION ERREUR !!! ###     : Erreur de configuration Reseau : Verifier les parametres reseau de l'Arduino UNO R4 \033[0;39;49m"));  cmdLine.println(); } 

  cmdLine.print( F("  - L'adresse IP. . . . . . . . . . . . : ")); cmdLine.println( ip);
  cmdLine.print( F("  - Le Masque de sous reseau (subnet) . : ")); cmdLine.println( subnet);
  cmdLine.print( F("  - La Passerelle (gateway) . . . . . . : ")); cmdLine.println( gateway);
  cmdLine.print( F("  - DNS . . . . . . . . . . . . . . . . : ")); cmdLine.println( "1.1.1.1" );


if (arg != 0)
{  
  cmdLine.println(F("\033[1;31;40m"));  // Texte en brun 
  cmdLine.println(F(" Configuration Reseau en RAM  ( Pour sauver la configuration en EEPROM : taper save )"));                     cmdLine.println(F("\033[0;39;49m"));  // Texte normal
  cmdLine.print(  F("  - DHCP. . . . . . . . . . . . . . . . : ")); cmdLine.println( conf.dhcp ? "ON" : "OFF");
  cmdLine.print(  F("  - L'adresse IP. . . . . . . . . . . . : ")); cmdLine.println( IPAddress(conf.ip));
  cmdLine.print(  F("  - Le Masque de sous reseau (subnet) . : ")); cmdLine.println( IPAddress(conf.subnet));
  cmdLine.print(  F("  - La Passerelle (gateway) . . . . . . : ")); cmdLine.println( IPAddress(conf.gateway));
  cmdLine.print(  F("  - MAC Address . . . . . . . . . . . . : ")); PrintMAC(conf.mac);  cmdLine.println(); 
  // Test si l'adresse MC a ete changer --> Attention si plusieurs Arduino UNO avec la meme adresse MAC sur les reseaux risque de BUG 
  if ( conf.mac[0] == default_conf.mac[0] && conf.mac[1] == default_conf.mac[1] && conf.mac[2] == default_conf.mac[2] && conf.mac[3] == default_conf.mac[3] && conf.mac[4] == default_conf.mac[4] && conf.mac[5] == default_conf.mac[5] )
 {cmdLine.println(F("\033[;37;41m     ###     ATTENTION  !!! ###     : Il Faut changer l'adresse MAC de l'Arduino UNO R4 \033[0;39;49m"));  cmdLine.println(); } 
  cmdLine.print(  F("  - Serveur WEB : PORT. . . . . . . . . : ")); cmdLine.println( conf.port);
					

  cmdLine.println(F("\033[1;31;40m"));  // Texte en brun 
  cmdLine.println(F(" Configuration Reseau enregistre en EEPROM  ( Cette configuration n'est valide qu'apres un RESET : taper reset )"));   cmdLine.println(F("\033[0;39;49m"));  // Texte normal
// Get configuration from EEPROM (EEPROM address: adressBase)
  EEPROM.get(adressBase, conf_TMP);
  cmdLine.print(  F("  - DHCP. . . . . . . . . . . . . . . . : ")); cmdLine.println( conf_TMP.dhcp ? "ON" : "OFF");
  cmdLine.print(  F("  - L'adresse IP. . . . . . . . . . . . : ")); cmdLine.println( IPAddress(conf_TMP.ip));
  cmdLine.print(  F("  - Le Masque de sous reseau (subnet) . : ")); cmdLine.println( IPAddress(conf_TMP.subnet));
  cmdLine.print(  F("  - La Passerelle (gateway) . . . . . . : ")); cmdLine.println( IPAddress(conf_TMP.gateway));
  cmdLine.print(  F("  - MAC address . . . . . . . . . . . . : ")); PrintMAC(conf_TMP.mac);  cmdLine.println();  
  cmdLine.print(  F("  - Serveur WEB : PORT. . . . . . . . . : ")); cmdLine.println( conf_TMP.port);
}

  cmdLine.println(F("\033[1;31;40m"));  // Texte en brun 
  cmdLine.println( F(" Environnement"));                            cmdLine.println(F("\033[0;39;49m"));  // Texte normal
// Date time
  if ( etatWAN == 1 )
      { 
          timeClient.update();
          cmdLine.print(  F("  - Time : ")); cmdLine.println(  timeClient.getFormattedTime() );                      // GET Heure : 12:22:01
          cmdLine.print(  F("  - Date : ")); cmdLine.println(  timeClient.getFormattedDate() );                      // Get Date : 2020-03-21T02:28:47Z
          cmdLine.print(  F("  - Date du dernier reboot : ")); cmdLine.println(  conf.timeReboot ); 
      }
  else Serial.println(F("  - WAN ERROR !! -- Serveur NTP (Real Time) : OFF !"));

// Info RAM
  cmdLine.print(  F("  - ")); RamFree(nullptr);

/* 
  cmdLine.println( F(" Configuration en cours variable conf "));
  cmdLine.print( F("   - IP   : "));  Serial.print(conf.ip[0]);    Serial.print(".");  Serial.print(conf.ip[1]); Serial.print(".");  Serial.print(conf.ip[2]);   Serial.print(".");   Serial.println(conf.ip[3]);                       
  cmdLine.print( F("   - MsR  : "));  Serial.print(conf.subnet[0]);    Serial.print(".");  Serial.print(conf.subnet[1]); Serial.print(".");  Serial.print(conf.subnet[2]);   Serial.print(".");   Serial.println(conf.subnet[3]);                       
  cmdLine.print( F("   - Pass : "));  Serial.print(conf.gateway[0]);    Serial.print(".");  Serial.print(conf.gateway[1]); Serial.print(".");  Serial.print(conf.gateway[2]);   Serial.print(".");   Serial.println(conf.gateway[3]);                      
*/
}
 ////////////////////////////////////////////////////////////////////////////////////////////////////
//           Fonctions SYSTEM
////////////////////////////////////////////////////////////////////////////////////////////////////
 
 
////////////////////////////////////////////////////////////////////////////////////////////////////
//           TELNET Connexion
////////////////////////////////////////////////////////////////////////////////////////////////////
/*************************************************************************************************************
* Sous Programme : SetCmdTelnetPass
*=============================================================================================================*
* Fonction   : Modifie le mot de passe d'acces au CLI Serial et Telnet
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nouvelle chaine de caracteres (mot de passe)
* Sortie     : cmdLine.println  -- confirmation du nouveau mot de passe
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.telnetPassword[40]  -- chaine du mot de passe (max 39 caracteres + '')
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : password <nouveauMotDePasse>
*************************************************************************************************************/
void SetCmdTelnetPass(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  for (size_t i = 0 ; i < sizeof(conf.telnetPassword) ; ++i) {
    conf.telnetPassword[i] = arg[i];
  }
  cmdLine.print( F("Nouveau Password : "));  cmdLine.println( conf.telnetPassword);
}

/*************************************************************************************************************
* Sous Programme : LoginCmdTelnetConnexion
*=============================================================================================================*
* Fonction   : Gere le verrouillage / deverrouillage du CLI et la verification du mot de passe
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "ON" / "OFF" pour activer/desactiver le verrou
*                               -- <motDePasse> pour tenter de se connecter
* Sortie     : cmdLine.println  -- confirmation ou refus dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.login_cmdTelnetSwitch : 0 = CLI ouvert / 1 = CLI verrouille
*   nbrEssaiLoginTelnet        : compteur de delai anti-brute-force (double a chaque erreur, max 600000s)
*   previousMillisTelnet       : horodatage du dernier essai (millis())
*-------------------------------------------------------------------------------------------------------------
* Machine d'etat :
*   arg == "ON"          -> verrouille le CLI (conf.login_cmdTelnetSwitch = 1)
*   arg == "OFF"         -> deverrouille le CLI (conf.login_cmdTelnetSwitch = 0)
*   arg == motDePasse    -> deverrouille si correct, sinon double le delai d'attente
*   delai ecrase         -> affiche le temps restant avant nouvel essai (sec / min / h / jours)
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : login ON / login OFF / login <motDePasse>
*************************************************************************************************************/
void LoginCmdTelnetConnexion(const char *arg) {
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  if ( strcmp (arg, "ON") == 0 || strcmp (arg, "on") == 0 )                                        // strcmp compare deux chaine de caractere http://manpagesfr.free.fr/man/man3/strcmp.3.html 
     {                                                                                             // http://koor.fr/C/cstring/strcmp.wp
         conf.login_cmdTelnetSwitch = 1;
         previousMillisTelnet = millis();
         nbrEssaiLoginTelnet = 5; 
         EEPROM.put(adressBase, conf);
         //EEPROM.commit(); 
         cmdLine.print( F("System IoTOS verouille avec le mot de passe : "));  cmdLine.println( conf.telnetPassword);
      }
  else if ( strcmp (arg, "OFF") == 0 || strcmp (arg, "off") == 0 )                                 // strcmp compare deux chaine de caractere http://manpagesfr.free.fr/man/man3/strcmp.3.html 
     {                                                                                             // http://koor.fr/C/cstring/strcmp.wp
         conf.login_cmdTelnetSwitch = 0;
         EEPROM.put(adressBase, conf);
         //EEPROM.commit(); 
         cmdLine.print( F("System IoTOS deverouille avec le mot de passe : "));  cmdLine.println( conf.telnetPassword);
      }
  // Verification du password pour le Login

/*
// Verification du mot de passe : Methode simple = attacable par brut force
  if (strcmp (arg, conf.telnetPassword) == 0)                                  // Compare la chaine de caractere arg avec le mot de passe
  {  
       conf.login_cmdTelnetSwitch = !conf.login_cmdTelnetSwitch;
       EEPROM.put(adressBase, conf);
       if ( conf.login_cmdTelnetSwitch == 0 ) cmdLine.println( F("Connexion autorisee !! ")); 
       else                                   cmdLine.println( F("Tapez Login password pour reactiver le Login au cmdLine et au Telnet ")); 
  }
  else
  {
       cmdLine.println( F("Connexion refusee : mot de passe FAUX !!!!!"));
  }
 */   

// Verification du mot de passe : Methode doublage de temps entre chaque essai
  else if (strcmp (arg, conf.telnetPassword) == 0)                                                 // Compare la chaine de caractere arg avec le mot de passe
  {  
      // Code VRAI 
      if ( conf.login_cmdTelnetSwitch == 0 ) { cmdLine.println( F("IoTOS deja logue .... "));  return; }
      conf.login_cmdTelnetSwitch = 0;
      nbrEssaiLoginTelnet = 5;                                                                     // re intitialise le temps a 10 secondes
      cmdLine.println( F("\033[1;32;40m")); // Vert sur fond noir 
      cmdLine.println( F("IoTOS UNLOCKED !!"));
      cmdLine.println( F("Connexion autorisee...")); 
      cmdLine.println( F("\033[0;39;49m"));                                           // Serial color defaut       
  }
  else // Code FAUX
  {
       cmdLine.println( F("Connexion Telnet/Serial refusee : \033[;37;41m mot de passe FAUX !!!!!  \033[0;39;49m"));     

       // Allonge le delais de *2 a chque erreur de login
       if (nbrEssaiLoginTelnet < 600000)  nbrEssaiLoginTelnet = nbrEssaiLoginTelnet*2;             // multiplie le temps par deux mais < 600 000seconde : 7 jours max  //1000*60*60*24*7 = 604 800 000
                                                                                                   // 5 10 20 40 80 160 320 640 1280 
                                                                                                         
        // DEBUG
       //Serial.print(F("-- nbrEssaiLoginTelnet "));           Serial.println (nbrEssaiLoginTelnet, DEC);
       //Serial.print(F("-- millis "));                        Serial.println (millis(), DEC);
       //Serial.print(F("-- previousMillisTelnet "));          Serial.println (previousMillisTelnet, DEC);
       //Serial.print(F("-- millis - previousMillisTelnet ")); Serial.println ( (millis() - previousMillisTelnet ), DEC);
       //Serial.print(F("-- timeLogTelnet ")); Serial.println ( (float) nbrEssaiLoginTelnet - ( millis() - previousMillisTelnet ) / 1000, DEC);                                                                                                         
                                                                                                         
                                                                                                                                                                                                              
        // Calcul tu temps entre deux essais : 
        // verifie qu'il ne soit pas NEGATIF : que l'on retape le code apres le temps du blocage     
       if ( (float) nbrEssaiLoginTelnet - ( millis() - previousMillisTelnet ) / 1000 < 0) 
            {  cmdLine.println( F("verifier votre mot de passe ")); 
               previousMillisTelnet = millis();
               return;
            }
       
       unsigned long timeLogTelnet = 0;       
       timeLogTelnet = (float) nbrEssaiLoginTelnet - ( millis() - previousMillisTelnet ) / 1000 ;

       // Affichage des valeurs de delais en Secondes / minutes / heures / jours
       cmdLine.print( F("Nouvelle connexion possible dans .... "));  
       if (timeLogTelnet <= 60) 
             { cmdLine.print( timeLogTelnet);                  cmdLine.println( F(" Secondes" )); }
       else if ((timeLogTelnet > 60) && (timeLogTelnet <= 60*60 )) 
             { cmdLine.print( timeLogTelnet/60);               cmdLine.println( F(" Minutes" )); }
       else if ((timeLogTelnet > 60*60) && (timeLogTelnet <= 60*60*24 ))
             { cmdLine.print( timeLogTelnet/(3600));           cmdLine.println( F(" Heures" )); }
       else  { cmdLine.print( timeLogTelnet/(3600*24));        cmdLine.println( F(" Jours" )); }       


       previousMillisTelnet = millis();                                     //http://arlotto.univ-tln.fr/arduino/article/utilisez-correctement-la-fonction
  }    
}
/*************************************************************************************************************
* Sous Programme : Exit
*=============================================================================================================*
* Fonction   : Ferme la session ouverte et reactive le verrou de connexion CLI/Telnet
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : cmdLine.println  -- confirmation dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.login_cmdTelnetSwitch : repasse a 1 (verrou actif) via LoginCmdTelnetConnexion("on")
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : exit
*************************************************************************************************************/
void Exit(const char *arg) { (void)arg;
  cmdLine.print( F("Commande confirmee : "));
  LoginCmdTelnetConnexion("on");
  cmdLine.println( F(" Login / Pass active"));
}


 ////////////////////////////////////////////////////////////////////////////////////////////////////
//           Fonctions de base
////////////////////////////////////////////////////////////////////////////////////////////////////
/*************************************************************************************************************
* Sous Programme : Name
*=============================================================================================================*
* Fonction   : Modifie le nom du systeme affiche dans l'interface CLI et dans PrintConf
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- chaine du nouveau nom (max 39 caracteres)
* Sortie     : cmdLine.println  -- confirmation du nouveau nom
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.nameSystem[40]  -- nom du systeme (chaine de 40 octets)
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : name <NouveauNom>
*************************************************************************************************************/
void Name(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
   cmdLine.print( F("Commande confirmee : "));

   for (size_t i = 0 ; i < sizeof(conf.nameSystem) ; ++i) {
     conf.nameSystem[i] = arg[i];
   }

   cmdLine.print( F("Nouveau Nom Entreprise : "));        cmdLine.println( conf.nameSystem);  
 }

/*************************************************************************************************************
* Sous Programme : SetPing
*=============================================================================================================*
* Fonction   : Envoie 4 paquets ICMP Echo Request vers une IP ou un nom DNS et affiche les statistiques
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- adresse IP ("192.168.0.1") ou nom DNS ("google.com")
* Sortie     : cmdLine.println  -- resultats ping + statistiques (min/max/moyenne) dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   nbPing    : nombre de paquets envoyes (fixe a 4)
*   minTime   : temps aller-retour minimum (ms)
*   maxTime   : temps aller-retour maximum (ms)
*   sumTime   : somme des temps pour calcul de la moyenne (ms)
*   received  : nombre de reponses recues
*   buffer[]  : buffer char[256] global pour sprintf
*-------------------------------------------------------------------------------------------------------------
* Algorithme :
*   Pour chaque paquet (i=0 a nbPing-1) :
*     - Envoie ping ICMP (EthernetICMPEchoReply)
*     - Si SUCCESS : affiche IP + temps + TTL, met a jour min/max/sum
*     - Sinon      : affiche "Delai d'attente depasse."
*     - Attend 1000 ms
*   Affiche statistiques finales
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : ping <IP ou URL>
*************************************************************************************************************/
void SetPing(const char *arg) {  (void)arg;
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  //if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  cmdLine.print( F("Commande confirmee : "));
  // Creation d'un objet IPAddress
  IPAddress ip;

  // Conversion de la chaine "arg" (ex: "192.168.0.253") en adresse IP
  ip.fromString(arg);

  // Nombre de paquets ping  envoyer
  const int nbPing = 4;

  // Variables pour calculer les statistiques
  long minTime = 999999;   // temps minimum (initialise tres grand)
  long maxTime = 0;        // temps maximum
  long sumTime = 0;        // somme des temps pour calcul moyenne
  int received = 0;        // nombre de reponses recues

  // Affichage du message d'introduction (comme la commande ping Windows)
  cmdLine.print("Envoi d'une requete 'Ping' ");
  cmdLine.print(arg);
  cmdLine.println(" avec 32 octets de donnees :");

  // Boucle qui envoie plusieurs pings
  for(int i=0;i<nbPing;i++)
  {
    // Envoi d'un seul paquet ICMP Echo Request
    EthernetICMPEchoReply echoReply = ping(ip,1);

    // Si une reponse ICMP Echo Reply est recue
    if(echoReply.status == SUCCESS)
    {
      // Calcul du temps aller-retour
      // echoReply.data.time = timestamp envoye dans le paquet
      // millis() = temps actuel Arduino
      long t = millis() - echoReply.data.time;

      // Construction de la ligne de reponse
      sprintf(buffer,
        "Reponse de %d.%d.%d.%d : octets=%d temps=%ld ms TTL=%d",
        echoReply.addr[0],   // IP repondante
        echoReply.addr[1],
        echoReply.addr[2],
        echoReply.addr[3],
        REQ_DATASIZE,        // taille du payload ICMP
        t,                   // temps aller-retour
        echoReply.ttl);      // TTL du paquet IP recu

      // Envoi de la ligne vers le terminal (telnet ou serie)
      cmdLine.println(buffer);

      // Increment du compteur de reponses
      received++;

      // Mise  jour du temps minimum
      if(t < minTime) minTime = t;

      // Mise  jour du temps maximum
      if(t > maxTime) maxTime = t;

      // Ajout du temps pour calcul de la moyenne
      sumTime += t;
    }
    else
    {
      // Aucun paquet recu dans le delai imparti
      cmdLine.println("Delai d'attente de la demande depasse.");
    }

    // Attente 1 seconde entre chaque ping (comme Windows)
    delay(1000);
  }

  // Ligne vide pour la lisibilite
  cmdLine.println();

  // Affichage du titre des statistiques
  sprintf(buffer, "Statistiques Ping pour %s :", arg);
  cmdLine.println(buffer);

  // Affichage du nombre de paquets envoyes / recus / perdus
  sprintf(buffer,   "    Paquets : envoyes = %d, recus = %d, perdus = %d", nbPing, received, nbPing - received);
  cmdLine.println(buffer);

  // Si au moins une reponse a ete recue
  if(received>0)
  {
    // Affichage de l'entete des temps
    sprintf(buffer,
    "Duree approx. aller-retour en millisecondes :");
    cmdLine.println(buffer);

    // Calcul et affichage des statistiques
    sprintf(buffer,
    "    Minimum = %ldms, Maximum = %ldms, Moyenne = %ldms",
    minTime,             // temps minimum
    maxTime,             // temps maximum
    sumTime/received);   // moyenne

    cmdLine.println(buffer);
  }

}

 ////////////////////////////////////////////////////////////////////////////////////////////////////
 /***********************************************************************************************************************************************************
* Sous Programme : netstat : Info Socket
************************************************************************************************************************************************************
> netstat
Commande confirmee :
Sock Mode   Status        LocalIP         LocalPort  RemoteIP        RemotePort
0    UDP    UDP           192.168.0.10    1337       85.199.214.102  123
1    TCP    LISTEN        192.168.0.10    23         1.1.1.1         53
2    TCP    ESTABLISHED   192.168.0.10    21         192.168.0.171   50875
3    TCP    CLOSED        192.168.0.10    50009      192.168.0.171   50876
4    TCP    LISTEN        192.168.0.10    80         192.168.0.171   50869
5    TCP    LISTEN        192.168.0.10    21         192.168.0.171   50870
6    TCP    LISTEN        192.168.0.10    50009      -               -
7    ---    CLOSED        192.168.0.10    0          -               -
***********************************************************************************************************************************************************/
//#include <Ethernet.h>
//#include <utility/w5100.h>
const char* socketStatus(uint8_t s)
{
  switch (s)
  {
    case 0x00: return "CLOSED";
    case 0x13: return "INIT";
    case 0x14: return "LISTEN";
    case 0x17: return "ESTABLISHED";
    case 0x1C: return "CLOSE_WAIT";
    case 0x22: return "UDP";
    case 0x32: return "IPRAW";
    case 0x42: return "MACRAW";
    default:   return "UNKNOWN";
  }
}

const char* socketMode(uint8_t m)
{
  switch (m & 0x0F)
  {
    case 0x01: return "TCP";
    case 0x02: return "UDP";
    case 0x03: return "IPRAW";
    case 0x04: return "MACRAW";
    default:   return "---";
  }
}
void printPad(const char *txt, uint8_t width)
{
  uint8_t len = strlen(txt);

  Serial.print(txt);
  clientTelnet.print(txt);

  while (len < width)
  {
    Serial.print(' ');
    clientTelnet.print(' ');
    len++;
  }
}

void printPadNum(uint32_t val, uint8_t width)
{
  char buf[16];
  ultoa(val, buf, 10);
  printPad(buf, width);
}

void printPadIP(byte ip[4], uint8_t width)
{
  char buf[20];
  sprintf(buf,"%u.%u.%u.%u",ip[0],ip[1],ip[2],ip[3]);
  printPad(buf,width);
}

/*************************************************************************************************************
* Sous Programme : Netstat
*=============================================================================================================*
* Fonction   : Affiche l'etat de toutes les sockets du W5500 (8 sockets)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : Serial.println + clientTelnet.println -- tableau des sockets dans les deux terminaux
*-------------------------------------------------------------------------------------------------------------
* Variables lues (registres W5500) :
*   SnSR : status de la socket (CLOSED/LISTEN/ESTABLISHED/UDP/...)
*   SnMR : mode de la socket (TCP/UDP/IPRAW/MACRAW)
*   SnPORT : port local
*   SnDIPR : IP distante
*   SnDPORT : port distant
*-------------------------------------------------------------------------------------------------------------
* Colonnes affichees : Sock | Mode | Status | LocalIP | LocalPort | RemoteIP | RemotePort
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : netstat
*************************************************************************************************************/
void Netstat(const char *arg) {    (void)arg;
if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }
  cmdLine.print( F("Commande confirmee : "));
  cmdLine.println();

  // entete
  printPad("Sock",5);
  printPad("Mode",7);
  printPad("Status",14);
  printPad("LocalIP",16);
  printPad("LocalPort",11);
  printPad("RemoteIP",16);
  printPad("RemotePort",11);

  Serial.println();
  clientTelnet.println();

  // lecture des sockets W5500
  for (int s=0; s<8; s++)
  {
    uint8_t status = W5100.readSnSR(s);
    uint8_t mode   = W5100.readSnMR(s);

    uint16_t lport = W5100.readSnPORT(s);

    byte lip[4];
    byte rip[4];

    W5100.readSnDIPR(s,rip);

    // IP locale = IP Arduino
    lip[0]=Ethernet.localIP()[0];
    lip[1]=Ethernet.localIP()[1];
    lip[2]=Ethernet.localIP()[2];
    lip[3]=Ethernet.localIP()[3];

    uint16_t rport = W5100.readSnDPORT(s);

    // impression ligne
    printPadNum(s,5);
    printPad(socketMode(mode),7);
    printPad(socketStatus(status),14);

    printPadIP(lip,16);
    printPadNum(lport,11);

    if(rip[0]!=0)
      printPadIP(rip,16);
    else
      printPad("-",16);

    if(rip[0]!=0)
      printPadNum(rport,11);
    else
      printPad("-",11);

    Serial.println();
    clientTelnet.println();
  }

  cmdLine.println();
}

////////////////////////////////////////////////////////////////////////////////////////////////////
/***********************************************************************************************************************************************************
* Sous Programme : tracert : Realise un traceroute simplifie comme la commande "tracert" sous Windows
************************************************************************************************************************************************************
* Principe :
* On envoie des ping ICMP avec un TTL croissant. Chaque routeur decremente le TTL.
* Quand TTL arrive  0, le routeur renvoie ICMP "Time Exceeded".
* Ainsi on decouvre chaque routeur du chemin.      -               -
***********************************************************************************************************************************************************/
void PrintPadTracert(const char* txt, uint8_t width)
{
  uint8_t len = strlen(txt);

  cmdLine.print(txt);

  while (len < width)
  {
    cmdLine.print(' ');
    len++;
  }
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*************************************************************************************************************
* Sous Programme : Tracert
*=============================================================================================================*
* Fonction   : Realise un traceroute simplifie vers une IP ou un nom DNS (comme "tracert" sous Windows)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- adresse IP ou nom DNS de la cible
* Sortie     : cmdLine.println  -- tableau hop par hop (TTL, temps x3, IP du routeur) dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Algorithme (principe TTL croissant) :
*   Pour TTL = 1 a maxHop (30) :
*     - Configure le TTL du ping
*     - Envoie 3 paquets ICMP
*       - Si reponse : affiche le temps en ms
*       - Sinon      : affiche "*"
*     - Affiche l'IP du routeur ayant repondu
*     - Si IP cible atteinte : affiche "Trace complete." et sort
*   Chaque routeur decremente le TTL ; quand TTL=0, le routeur renvoie ICMP "Time Exceeded"
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   ip       : adresse IP resolue de la cible (IPAddress)
*   maxHop   : nombre maximum de sauts (fixe a 30)
*   answered : booleen indiquant si une reponse a ete recue au saut courant
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : tracert <IP ou URL>
*************************************************************************************************************/
void Tracert(const char *arg) {
  //if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));

  IPAddress ip;

  //------------------------------------------------------------
  // 1) determiner si arg est une IP ou un nom DNS
  //------------------------------------------------------------
  if (ip.fromString(arg))
  {
    // arg est dej une adresse IP
  }
  else
  {
    // resolution DNS
    if (dns.getHostByName(arg, ip) != 1)
    {
      cmdLine.println("Erreur DNS");
      return;
    }
  }

  // entete
  cmdLine.print("Traceroute vers ");
  cmdLine.print(arg);
  cmdLine.print(" [");
  cmdLine.print(ip);
  cmdLine.println("]");

  const int maxHop = 30;

  // boucle TTL
  for (int ttl = 1; ttl <= maxHop; ttl++)
  {
    char buf[16];

    // colonne hop
    sprintf(buf,"%d",ttl);
    PrintPadTracert(buf,4);

    ping.setTTL(ttl);

    bool answered=false;
    EthernetICMPEchoReply echoReply;

    // 3 essais
    for (int i=0;i<3;i++)
    {
      unsigned long start = millis();

      ping(ip,1,echoReply);

      long t = millis() - start;

      if (echoReply.status == SUCCESS)
      {
        sprintf(buf,"%ld ms",t);
        PrintPadTracert(buf,10);
        answered=true;
      }
      else
      {
        PrintPadTracert("*",10);
      }
    }
    
    // affichage IP
    if (answered)
    {
      cmdLine.print("  ");

      cmdLine.print(echoReply.addr[0]); cmdLine.print(".");
      cmdLine.print(echoReply.addr[1]); cmdLine.print(".");
      cmdLine.print(echoReply.addr[2]); cmdLine.print(".");
      cmdLine.println(echoReply.addr[3]);

      if (ip == IPAddress(echoReply.addr))
      {
        cmdLine.println("Trace complete.");
        break;
      }
    }
    else
    {
      cmdLine.println("  Delai d'attente depasse.");
    }
  }
  cmdLine.println();
}

/*************************************************************************************************************
* Sous Programme : SetDatalogger
*=============================================================================================================*
* Fonction   : Active ou desactive l'enregistrement des evenements dans le fichier CSV datalogger
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "ON" / "on"  ou  "OFF" / "off"
* Sortie     : cmdLine.println  -- confirmation dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.dataloggerSwitch : 0 = datalogger inactif / 1 = datalogger actif
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : datalogger on / datalogger off
*************************************************************************************************************/
void SetDatalogger(const char *arg) {                 // arg : chaine de caracteres
 if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }
 if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));
//  cmdLine.print( F("argument : "));Serial.println(arg);

  if ( strcmp (arg, "ON") == 0 || strcmp (arg, "on") == 0 )                                        // strcmp compare deux chaine de caractere http://manpagesfr.free.fr/man/man3/strcmp.3.html 
     {                                                                                             // http://koor.fr/C/cstring/strcmp.wp
        cmdLine.println( F("Datalogger est ON"));
        conf.dataloggerSwitch = 1;
     }

  else if ( strcmp (arg, "OFF") == 0 || strcmp (arg, "off") == 0 )
     {               
         cmdLine.println( F("Datalogger est OFF"));
         conf.dataloggerSwitch = 0;
     }
  else cmdLine.println( F("Commande syntaxe ERROR ")); 
}

/*************************************************************************************************************
* Sous Programme : Select
*=============================================================================================================*
* Fonction   : Selectionne le fichier CSV actif pour le datalogger
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nom du fichier avec extension (ex: "datalogger.csv")
* Sortie     : cmdLine.println  -- confirmation du nom de fichier selectionne
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.fichierEnCours[40]  -- chaine du nom de fichier actif (max 39 caracteres + '')
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : select <NomFichier.ext>
*************************************************************************************************************/
void Select(const char *arg) {  // arg : chaine de caracteres
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; } 
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;} 
  cmdLine.print( F("Commande confirmee : "));
  for (int i = 0 ; i < 40 ; ++i) {
    conf.fichierEnCours[i] = arg[i];
  } 
  cmdLine.print( F("Le Fichier en cours pour le Datalogger est : "));  cmdLine.println( conf.fichierEnCours);
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////
////////////    Micro SD / SPIFFS LittleFS  Fonctions    dir, rename, del, type, Edit, copy, format
//////////////////////////////////////////////////////////////////////////////////////////////////////////////
/* ************************************************************************************************************
* Sous Programme
* Fonction : SD Fonctions
***************************************************************************************************************
    File file = SD.open("/f.txt", "w");
     File var = SD.open(arg, FILE_WRITE);

Lecture
FsFile file = sd.open("config.txt", O_WRONLY | O_CREAT | O_TRUNC);
Signification :
O_WRONLY -> ecriture seule
O_CREAT -> cree le fichier s’il n’existe pas
O_TRUNC -> efface le contenu existant

Ecriture

FsFile file = sd.open("test.txt", O_RDWR | O_CREAT | O_APPEND); off
FsFile file = sd.open("log.txt", O_WRONLY | O_CREAT | O_APPEND);
O_APPEND positionne automatiquement a la fin
+----------------+-------------------------------------------+------------------------------------------+
| Flag           | Signification                             | Exemple                                  |
+----------------+-------------------------------------------+------------------------------------------+
| O_READ         | Lecture seule                             | sd.open("file.txt", O_READ)              |
| O_WRITE        | Ecriture                                  | sd.open("file.txt", O_WRITE)             |
| O_RDWR         | Lecture + ecriture                        | sd.open("file.txt", O_RDWR)              |
| O_CREAT        | Cree le fichier s'il n'existe pas         | sd.open("file.txt", O_WRITE | O_CREAT)   |
| O_TRUNC        | Vide le fichier existant a l'ouverture    | sd.open("file.txt", O_WRITE | O_TRUNC)   |
| O_APPEND       | Ajoute a la fin du fichier                | sd.open("file.txt", O_WRITE | O_APPEND)  |
| O_WRONLY       | Ecriture seule                            | sd.open("file.txt", O_WRONLY)            |
+----------------+-------------------------------------------+------------------------------------------+

+---------------------------------------------------+----------------------------------------------+-------------------+
| Fonction                                          | Description                                  | Type / Retour     |
+---------------------------------------------------+----------------------------------------------+-------------------+
| open(const char* name, uint8_t oflag)             | Ouvre ou cree un fichier                     | bool              |
| openNext(SdFile* dir, uint8_t oflag)              | Ouvre le fichier suivant dans un repertoire  | bool              |
| close()                                           | Ferme le fichier                             | void              |
| read()                                            | Lit 1 octet                                  | int (-1 si fin)   |
| read(void* buf, size_t nbyte)                     | Lit plusieurs octets                         | size_t lu         |
| write(uint8_t val)                                | Ecrit 1 octet                                | size_t ecrit      |
| write(const void* buf, size_t nbyte)              | Ecrit un buffer                              | size_t ecrit      |
| seek(uint32_t pos)                                | Deplace le pointeur                          | bool              |
| position()                                        | Renvoie la position actuelle                 | uint32_t          |
| fileSize()                                        | Renvoie la taille du fichier                 | uint32_t          |
| getName(char* name, size_t size)                  | Recupere le nom du fichier                   | bool              |
| getModifyDateTime(uint16_t* date, uint16_t* time) | Renvoie date/heure FAT                       | bool              |
| truncate(uint32_t length)                         | Tronque le fichier                           | bool              |
| isDirectory()                                     | Verifie si c'est un repertoire               | bool              |
| mkdir(const char* path)                           | Cree un repertoire                           | bool              |
| remove()                                          | Supprime un fichier                          | bool              |
| exists(const char* path)                          | Verifie si le fichier existe                 | bool              |
| sync()                                            | Force l'ecriture sur la carte SD             | void              |
+---------------------------------------------------+----------------------------------------------+-------------------+
*/
/*************************************************************************************************************
* Sous Programme : Create
*=============================================================================================================*
* Fonction   : Cree un nouveau fichier sur la carte SD avec l'en-tete CSV du datalogger
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nom du fichier a creer (ex: "datalogger.csv")
* Sortie     : cmdLine.println  -- confirmation de creation + appel de Ddir()
*              Fichier cree sur la SD avec une ligne d'en-tete CSV (formatCSV)
*-------------------------------------------------------------------------------------------------------------
* Variables lues :
*   formatCSV  -- chaine constante de l'en-tete CSV ("Date ; Heure ; Etat LED")
*-------------------------------------------------------------------------------------------------------------
* Flags SdFat utilises : O_RDWR | O_CREAT | O_APPEND
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : create <NomFichier.ext>
*************************************************************************************************************/
void Create(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));
//  cmdLine.print( F("argument : "));Serial.println(arg);
  FsFile fileCreate;

  // Ouvre le fichier pour ecriture (creation si inexistant)
  if (!fileCreate.open(arg, O_RDWR | O_CREAT | O_APPEND)) {
      cmdLine.println( F("Impossible d'ouvrir le fichier pour ecriture."));
      return;
  }

  // criture de la ligne CSV
  fileCreate.write((uint8_t*)formatCSV, strlen(formatCSV));
  fileCreate.write('\n');  // equivalent println()

  // Force l'ecriture et ferme le fichier
  fileCreate.flush();
  fileCreate.close();

  cmdLine.print( F("Fichier cree : "));
  cmdLine.println( arg);
  Ddir(nullptr);
}
/*************************************************************************************************************
* Sous Programme : Ddir
*=============================================================================================================*
* Fonction   : Affiche les informations de la carte SD et la liste des fichiers (equivalent "dir" MSDOS)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : cmdLine.println  -- informations SD + liste des fichiers dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Informations affichees :
*   - Type de carte SD (SDSC v1/v2 / SDHC-SDXC)
*   - Type FAT (FAT16 / FAT32)
*   - Taille totale (MB) et espace utilise (octets / Ko)
*   - Taille de cluster (octets)
*   - Liste des fichiers : date / heure / taille / nom
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   typeSDcard      : type physique de la carte (uint8_t)
*   typeFAT         : type FAT (uint8_t : 16 ou 32)
*   sectors         : nombre total de secteurs physiques (uint32_t)
*   totalMB         : taille totale en MB (sectors / 2048)
*   sectorsPerCluster : nb de secteurs par cluster (uint32_t)
*   usedBytes       : somme des tailles de tous les fichiers (uint32_t)
*-------------------------------------------------------------------------------------------------------------
* Commandes declenchantes : dir / ls
*************************************************************************************************************/
void Ddir(const char *arg) {   (void)arg;  // arg non utilise (annul warning)
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  cmdLine.println(); 
  // Affichage des informations principales de la carte SD
  cmdLine.println( F("####-----  INFO CARTE SD  -----"));

  // ---------------------------------------------------------------------------
  // 1. Type de carte et type de FAT
  // ---------------------------------------------------------------------------
  unsigned char typeSDcard = sd.card()->type();     // Type physique de la carte SD
  unsigned char typeFAT    = sd.vol()->fatType();   // Type du systeme de fichiers FAT

  // ---------------------------------------------------------------------------
  // 2. Taille totale de la carte
  // ---------------------------------------------------------------------------
  uint32_t sectors = sd.card()->sectorCount();    // Nombre total de secteurs physiques
  // 1 secteur = 512 octets
  // Conversion en MB : (sectors * 512) / (1024*1024) 
  // ici, utilisation d'entiers pour Arduino pour eviter float
  uint32_t totalMB = sectors / 2048;  // 2048 secteurs = 1 MB

  // ---------------------------------------------------------------------
  // 3. Taille d'un cluster
  // ---------------------------------------------------------------------
  uint32_t sectorsPerCluster = sd.vol()->sectorsPerCluster();     // nb de secteurs par cluster
  //uint32_t clusterSizeBytes  = sectorsPerCluster * 512UL;
  // uint32_t freeClusters  = sd.vol()->freeClusterCount(); // tres lent sur cartes grandes // Comme le calcul de freeClusters est lent, on ne l'utilise pas

  // ---------------------------------------------------------------------
  // 4. Calcul taille reellement utilisee (somme des fichiers)
  // ---------------------------------------------------------------------
  uint32_t usedBytes = 0;
  SdFile directory;
  if (directory.open("/", O_RDONLY)) {
    SdFile file;
    while (file.openNext(&directory, O_RDONLY)) {
      usedBytes += file.fileSize();
      file.close();
    }
    directory.close();
  }


  // ---------------------------------------------------------------------------
  // 5. Affichage type carte et FAT
  // ---------------------------------------------------------------------------
  cmdLine.print( F("  Type de carte MicroSD : "));
  switch (typeSDcard) {
      case SD_CARD_TYPE_SD1:  cmdLine.println( F("SDSC v1"));      break;
      case SD_CARD_TYPE_SD2:  cmdLine.println( F("SDSC v2"));      break;
      case SD_CARD_TYPE_SDHC: cmdLine.println( F("SDHC/SDXC"));    break;
      default:                cmdLine.println( F("Inconnu"));
  }

  cmdLine.print( F("  Type FAT        : "));
  switch (typeFAT) {
      case 16: cmdLine.println( F("FAT16"));    break;
      case 32: cmdLine.println( F("FAT32"));    break;
      default: cmdLine.println( F("Inconnu"));
  }

  // ---------------------------------------------------------------------------
  // 6. Affichage des tailles
  // ---------------------------------------------------------------------------
  cmdLine.print( F("  Taille totale   : "));  cmdLine.print( totalMB);     cmdLine.println( F(" MB"));

  cmdLine.print( F("  Taille utilisee : "));  cmdLine.print( usedBytes);   cmdLine.print( F(" octets / "));
  cmdLine.print( usedBytes / 1024UL);         cmdLine.println( F(" Ko"));

  cmdLine.print( F("  Cluster size    : "));  cmdLine.print( sectorsPerCluster * 512UL);   cmdLine.println( F(" octets"));

  // ---------------------------------------------------------------------------
  // 6. Affichage du code d'erreur SD
  // ---------------------------------------------------------------------------
  //cmdLine.print( F("  Code erreur : 0x"));    Serial.println(sd.card()->errorCode(), HEX);   clientTelnet.println(sd.card()->errorCode(), HEX); 
  //cmdLine.print( F("  Data : 0x"));           Serial.println(sd.card()->errorData(), HEX);   clientTelnet.println(sd.card()->errorData(), HEX);
  cmdLine.println(); 
  
  cmdLine.println( F("####-----  dir Fichiers SD Card  -----"));
  // Fonction recursive pour lister les fichiers
  //SdFile directory;

  if (!directory.open("/", O_RDONLY)) {
    cmdLine.println( F("Impossible d'ouvrir le repertoire racine"));
    return;
  }

  SdFile file;

  while (file.openNext(&directory, O_RDONLY)) 
      {        
          char filename[64];
          file.getName(filename, sizeof(filename));
      
          uint32_t fileSize = file.fileSize();
      
          uint16_t date, time;
          file.getModifyDateTime(&date, &time); 
          // Decodage date FAT
          uint8_t day   = date & 0x1F;
          uint8_t month = (date >> 5) & 0x0F;
          uint16_t year = 1980 + (date >> 9);
      
          // Decodage heure FAT
          //uint8_t sec  = (time & 0x1F) * 2;
          uint8_t min  = (time >> 5) & 0x3F;
          uint8_t hour = (time >> 11) & 0x1F;
      
          // Affichage date
          char dateBuf[11];
          //sprintf(dateBuf, "%04u-%02u-%02u", year, month, day);
          sprintf(dateBuf, "%02u/%02u/%04u", day, month, year );   
          cmdLine.print( F("  "));
          cmdLine.print( dateBuf);
          cmdLine.print( F(" "));
      
          // Affichage heure
          char timeBuf[9];
          //sprintf(timeBuf, "%02u:%02u:%02u", hour, min, sec);
          sprintf(timeBuf, "%02u:%02u", hour, min);
          cmdLine.print( timeBuf);
          cmdLine.print( F(" "));
      
          // Taille alignee
          char sizeBuf[11];
          sprintf(sizeBuf, "%10lu", fileSize);
          cmdLine.print( sizeBuf);
          cmdLine.print( F(" "));
      
          // Nom du fichier
          cmdLine.println( filename);
      
          file.close();
      }
  directory.close();
  cmdLine.println(); 
}

/*************************************************************************************************************
* Sous Programme : RenameFichier
*=============================================================================================================*
* Fonction   : Renomme un fichier sur la carte SD
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "AncienNom NouveauNom" (deux mots separes par un espace)
* Sortie     : cmdLine.println  -- confirmation ou message d'erreur
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   str[40]      : copie locale de arg pour tokenisation (strtok)
*   OLDname[41]  : premier token = ancien nom de fichier
*   NEWname[41]  : second token = nouveau nom de fichier
*-------------------------------------------------------------------------------------------------------------
* Algorithme :
*   1. Tokenise arg (separateur espace)
*   2. Extrait OLDname et NEWname
*   3. Verifie l'existence de OLDname (sd.exists)
*   4. Appelle sd.rename(OLDname, NEWname)
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : rename <AncienNom> <NouveauNom>
*************************************************************************************************************/
void RenameFichier(const char *arg) {
  if (conf.login_cmdTelnetSwitch == 1) { cmdLine.println(F("Acces Refuse")); return; }
  if (arg == NULL || arg[0] == '\0') { cmdLine.println(F("Operande absent : commande annulee")); cmdLine.println(); return; }
  cmdLine.println(F("Commande confirmee ........ "));

  char str[41] = {0};
  char OLDname[41] = {0};
  char NEWname[41] = {0};
  strcpy(str, arg);
  const char *separators = " ";

  char *strToken = strtok(str, separators);
  if (strToken == nullptr) {
    cmdLine.println(F("Erreur Syntaxe: rename ancien (espace) nouveau"));
    return;
  }
  strcpy(OLDname, strToken);

  strToken = strtok(NULL, separators);
  if (strToken == nullptr) {
    cmdLine.println(F("Nouveau nom manquant !"));
    return;
  }
  strcpy(NEWname, strToken);          // ← BUG CORRIGÉ : copie du nouveau nom

  cmdLine.print(F(" Ancien nom : ")); cmdLine.println(OLDname);
  cmdLine.print(F(" Nouveau nom : ")); cmdLine.println(NEWname);

  // Vérification existence ancien fichier AVANT d'essayer d'ouvrir/renommer
  if (!sd.exists(OLDname)) {
    cmdLine.println(F("Le fichier source n'existe pas !"));
    return;
  }

  // Optionnel : avertir si le nouveau nom existe déjà
  if (sd.exists(NEWname)) {
    cmdLine.println(F("Erreur : le fichier destination existe deja !"));
    return;
  }

  if (sd.rename(OLDname, NEWname)) {
    cmdLine.println(F("Renommage OK"));
  } else {
    cmdLine.println(F("Erreur renommage"));
  }
}
/*************************************************************************************************************
* Sous Programme : Del
*=============================================================================================================*
* Fonction   : Supprime un fichier de la carte SD
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nom du fichier a supprimer
* Sortie     : cmdLine.println  -- confirmation de suppression ou message d'erreur
*-------------------------------------------------------------------------------------------------------------
* Algorithme :
*   1. Verifie si le fichier existe (sd.exists)
*   2. Tente sd.remove(arg)
*   3. En cas d'echec : affiche le code d'erreur SD en HEX
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : del <NomFichier>
*************************************************************************************************************/
void Del(const char *arg) {                 // arg : chaine de caracteres
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  cmdLine.print( F("Commande confirmee ........ "));
//  cmdLine.print( F("argument : "));Serial.println(arg);
    // Verifie si le fichier existe

 if (!sd.exists(arg)) {
        cmdLine.println( F("Le fichier n'existe pas !!"));
        return;
 }

    // Tente de supprimer
  if (sd.remove(arg)) {
        cmdLine.print( F("Fichier Supprime : "));
        cmdLine.println( arg);
  } 
  else {
        // chec suppression : peut etre dossier non vide ou nom invalide
        cmdLine.println( F("Erreur suppression fichier"));
        uint8_t err = sd.card()->errorCode();
        cmdLine.print( F("Code erreur SD : "));
        Serial.println(err, HEX); clientTelnet.println(err, HEX);                                 //cmdLine.println( err, HEX);
    }
}

/*************************************************************************************************************
* Sous Programme : Type
*=============================================================================================================*
* Fonction   : Affiche le contenu d'un fichier texte de la carte SD dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nom du fichier a lire
* Sortie     : cmdLine.print(char)  -- contenu octet par octet dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   fileType : objet FsFile pour la lecture
*   buffer[64] : buffer de lecture par blocs de 64 octets
*-------------------------------------------------------------------------------------------------------------
* Algorithme :
*   1. Verifie l'existence du fichier (sd.exists)
*   2. Ouvre en lecture seule (O_RDONLY)
*   3. Affiche la taille
*   4. Lit et affiche par blocs de 64 octets jusqu'a EOF
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : type <NomFichier>
*************************************************************************************************************/
void Type(const char *arg) {                 // arg : chaine de caracteres
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee ........ "));
//  cmdLine.print( F("argument : "));Serial.println(arg);
  FsFile fileType;
  uint8_t buffer[64];

  cmdLine.print( F("Lecture du fichier en cours : "));
  cmdLine.println( arg);

  if (!sd.exists(arg)) {
      cmdLine.println( F("Le fichier n'existe pas !!"));
      return;
  }

  // Ouverture en lecture seule
  if (!fileType.open(arg, O_RDONLY)) {
      cmdLine.println( F("Impossible d'ouvrir le fichier en lecture"));
      return;
  }

  // Affichage taille
  cmdLine.print( F("Taille du fichier : "));
  Serial.println( fileType.size() ); clientTelnet.println( fileType.size() );                 //cmdLine.println( fileType.size());
  cmdLine.println();

// Lecture optimisee
  if (!fileType.open(arg, O_RDONLY)) {
      cmdLine.println( F("Erreur ouverture"));
      return;
  }

  while (fileType.available()) {

      int n = fileType.read(buffer, sizeof(buffer));

      for (int i = 0; i < n; i++) {
          cmdLine.print( (char)buffer[i]);
      }
  }
 
  fileType.close();
  cmdLine.println();    cmdLine.println();
}

/*************************************************************************************************************
* Sous Programme : Edit
*=============================================================================================================*
* Fonction   : Ajoute une ligne de texte dans le fichier actif (conf.fichierEnCours) sur la carte SD
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- texte a ecrire dans le fichier (max ~1024 caracteres)
* Sortie     : cmdLine.println  -- affichage du contenu apres ecriture (appel de Type)
*              Ecriture caractere par caractere dans le fichier ouvert en O_RDWR
*-------------------------------------------------------------------------------------------------------------
* Variables lues :
*   conf.fichierEnCours[40]  -- nom du fichier actif selectionne par la commande "select"
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   fileEdit : objet FsFile pour l'ecriture
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : edit <texte a enregistrer>
*************************************************************************************************************/
void Edit(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));
  cmdLine.print( F("Ouverture du Fichier : "));            cmdLine.println( conf.fichierEnCours);
  FsFile fileEdit;

  // Ouvre le fichier pour ecriture (append ou overwrite selon besoin)
  if (!fileEdit.open(conf.fichierEnCours, O_RDWR )) {
      cmdLine.print( F("Impossible d'ouvrir le fichier pour ecriture."));
      return;
  }

  // criture caractere par caractere
  for (size_t i = 0; i < strlen(arg); ++i) {
      fileEdit.write(arg[i]);
  }

  // Force l'ecriture et ferme le fichier
  fileEdit.flush();
  fileEdit.close();

  // Affiche le contenu apres edition
  Type(conf.fichierEnCours);
} 

/*************************************************************************************************************
* Sous Programme : CopyFichier
*=============================================================================================================*
* Fonction   : Copie un fichier de la carte SD vers un nouveau fichier
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "FichierSource FichierDestination" (deux noms separes par un espace)
* Sortie     : cmdLine.println  -- progression "..." et confirmation de fin de copie
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   str[80]          : copie locale de arg pour tokenisation
*   nameOriginal[40] : nom du fichier source
*   nameCopie[40]    : nom du fichier destination
*   buffer[TBUFF]    : buffer de transfert (TBUFF = 1024 octets)
*   fileOriginal     : objet FsFile source (O_RDONLY)
*   fileCopie        : objet FsFile destination (O_WRITE | O_CREAT | O_TRUNC)
*-------------------------------------------------------------------------------------------------------------
* Algorithme :
*   1. Tokenise arg pour extraire source et destination
*   2. Verifie l'existence de la source (sd.exists)
*   3. Si la destination existe : la supprime (sd.remove)
*   4. Copie par blocs de 1024 octets (read -> write) jusqu'a EOF
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : copy <source> <destination>   /   copie <source> <destination>
*************************************************************************************************************/
void CopyFichier(const char *arg) {                 // arg : chaine de caracteres
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.println( F("Commande confirmee ........ "));  
  if ( arg[0] == 0 ) { cmdLine.println( F("Erreur : pas de nom de fichier "));     return;   }    // Detect de la variable arg est vide --> Sort de la fonction // Evite un reset de l'ESP a cause le la fonction strcpy qui tente de copier une fonction vide
  
   const int TBUFF = 1024;              // Taille du buffer de copie (1 KB)

  char str[80] = {0};                  // Buffer temporaire pour parser la commande
  char nameOriginal[40] = {0};         // Nom du fichier source
  char nameCopie[40] = {0};            // Nom du fichier destination

  // Copie de la ligne de commande dans buffer local
  strcpy(str, arg);

  // Separateur entre les parametres
  const char *separators = " ";

  // Extraction du premier mot = fichier original
  char *strToken = strtok(str, separators);
  if (strToken == NULL)
      {
        cmdLine.println( F("Erreur syntaxe"));
        return;
      }
  strcpy(nameOriginal, strToken);

  // Extraction du second mot = fichier copie
  strToken = strtok(NULL, separators);
  if (strToken == NULL)
      {
        cmdLine.println( F("Nom fichier copie manquant"));
        return;
      }
  strcpy(nameCopie, strToken);

  // Affichage info utilisateur
  cmdLine.println( F("Routine copie fichier..."));
  cmdLine.print( F("Fichier source : "));                   cmdLine.println( nameOriginal);

  cmdLine.print( F("Fichier destination : "));              cmdLine.println( nameCopie);

  // Verifie si le fichier source existe
  if (!sd.exists(nameOriginal))
      {
        cmdLine.println( F("Fichier original absent : copie abort"));
        return;
      }

  // Si le fichier destination existe on le supprime
  if (sd.exists(nameCopie))
      {
        sd.remove(nameCopie);
        cmdLine.println( F("Fichier destination existant : ecrasement"));
      }

  // Ouverture fichier source en lecture
  FsFile fileOriginal = sd.open(nameOriginal, O_RDONLY);

  if (!fileOriginal)
      {
        cmdLine.println( F("Erreur ouverture fichier source"));
        return;
      }

  // Ouverture fichier destination
  FsFile fileCopie = sd.open(nameCopie, O_WRITE | O_CREAT | O_TRUNC);

  if (!fileCopie)
      {
        cmdLine.println( F("Erreur creation fichier destination"));
        fileOriginal.close();
        return;
      }

  // Buffer de transfert
  uint8_t buffer[TBUFF];

  // Variables de lecture
  int n;

  // Lecture par bloc et ecriture
  while ((n = fileOriginal.read(buffer, TBUFF)) > 0)
      {
        fileCopie.write(buffer, n);        // ecrit exactement le nombre lu
        cmdLine.print( "...");  // progression
      }

  cmdLine.println( F(" END"));

  // Fermeture des fichiers
  fileOriginal.close();
  fileCopie.close();

  cmdLine.println( F("Fin de copie fichier"));
}


/*
  // Copie du contenu
  while (oldFile.available()) {
    newFile.write(oldFile.read());
  }

  oldFile.close();
  newFile.close();
*/

/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////    Configuration IP                                                                                                              /////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////////////////////////
// Configuration Reseau (IP / MSR / Passerelle) -- Serveur WEB -- Serveur FTP
////////////////////////////////////////////////////////////////////////////////////////////////////
/*************************************************************************************************************
* Sous Programme : LoginFTP
*=============================================================================================================*
* Fonction   : Modifie le login de connexion au serveur FTP
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nouveau login FTP (chaine de caracteres)
* Sortie     : cmdLine.println  -- confirmation du nouveau login
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.loginFTP[40]  -- login FTP (max 39 caracteres + '')
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : loginFTP <NouveauLogin>
*************************************************************************************************************/
void LoginFTP(const char *arg) {                 // arg : chaine de caracteres
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));
//  cmdLine.print( F("argument : "));Serial.println(arg);
     
  for (size_t i = 0 ; i < sizeof(conf.loginFTP) ; ++i) {
    conf.loginFTP[i] = arg[i];
  } 
cmdLine.print( F("Nouveau Login FTP : "));                   cmdLine.println( conf.loginFTP);
}
/*************************************************************************************************************
* Sous Programme : PasswordFTP
*=============================================================================================================*
* Fonction   : Modifie le mot de passe de connexion au serveur FTP
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nouveau mot de passe FTP (chaine de caracteres)
* Sortie     : cmdLine.println  -- confirmation du nouveau mot de passe
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.passwordFTP[40]  -- mot de passe FTP (max 39 caracteres + '')
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : passwordFTP <NouveauPassword>
*************************************************************************************************************/
void PasswordFTP(const char *arg) {                // arg : chaine de caracteres
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));
//  cmdLine.print( F("argument : "));Serial.println(arg);
     
  for (size_t i = 0 ; i < sizeof(conf.passwordFTP) ; ++i) {
    conf.passwordFTP[i] = arg[i];
  } 
cmdLine.print( F("Nouveau Password FTP : "));                cmdLine.println( conf.passwordFTP);
}

/*************************************************************************************************************
* Sous Programme : SetSrvWEB
*=============================================================================================================*
* Fonction   : Active ou desactive le serveur WEB
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "ON" / "on"  ou  "OFF" / "off"
* Sortie     : cmdLine.println  -- confirmation dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.SrvWEBswitch : 0 = serveur WEB inactif / 1 = serveur WEB actif
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : WEB on / WEB off
*************************************************************************************************************/
void SetSrvWEB(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));
//  cmdLine.print( F("argument : "));Serial.println(arg);

  if ( strcmp (arg, "ON") == 0 || strcmp (arg, "on") == 0 )                                        // strcmp compare deux chaine de caractere http://manpagesfr.free.fr/man/man3/strcmp.3.html 
     {                                                                                             // http://koor.fr/C/cstring/strcmp.wp
        cmdLine.println( F("Serveur WEB ON"));
        conf.SrvWEBswitch = 1;
     }

  else if ( strcmp (arg, "OFF") == 0 || strcmp (arg, "off") == 0 )
     {               
         cmdLine.println( F("Serveur WEB OFF"));
         conf.SrvWEBswitch = 0;
     }
  else cmdLine.println( F("Commande syntaxe ERROR ")); 
}

/*************************************************************************************************************
* Sous Programme : SetSrvFTP
*=============================================================================================================*
* Fonction   : Active ou desactive le serveur FTP
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "ON" / "on"  ou  "OFF" / "off"
* Sortie     : cmdLine.println  -- confirmation dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.SrvFTPswitch : 0 = serveur FTP inactif / 1 = serveur FTP actif
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : FTP on / FTP off
*************************************************************************************************************/
void SetSrvFTP(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));
//  cmdLine.print( F("argument : "));Serial.println(arg);

  if ( strcmp (arg, "ON") == 0 || strcmp (arg, "on") == 0 )                                        // strcmp compare deux chaine de caractere http://manpagesfr.free.fr/man/man3/strcmp.3.html 
     {                                                                                             // http://koor.fr/C/cstring/strcmp.wp
        cmdLine.println( F("Serveur FTP ON"));
        conf.SrvFTPswitch = 1;
     }

  else if ( strcmp (arg, "OFF") == 0 || strcmp (arg, "off") == 0 )
     {               
         cmdLine.println( F("Serveur FTP OFF"));
         conf.SrvFTPswitch = 0;
     }
  else cmdLine.println( F("Commande syntaxe ERROR ")); 
}

/*************************************************************************************************************
* Sous Programme : LoginWEB
*=============================================================================================================*
* Fonction   : Active ou desactive l'authentification (login/password) du site WEB
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "ON" / "on"  ou  "OFF" / "off"
* Sortie     : cmdLine.println  -- confirmation dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.loginWEB : 0 = authentification WEB desactivee / 1 = authentification WEB activee
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : loginweb on / loginweb off
*************************************************************************************************************/
void LoginWEB(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));

  
  if ( strcmp (arg, "ON") == 0 || strcmp (arg, "on") == 0 )                                        // strcmp compare deux chaine de caractere http://manpagesfr.free.fr/man/man3/strcmp.3.html 
     {                                                                                             // http://koor.fr/C/cstring/strcmp.wp
        cmdLine.println( F("Login site WEB : active OK "));
        conf.loginWEB = 1;
     }

  else if ( strcmp (arg, "OFF") == 0 || strcmp (arg, "off") == 0 )
     {        
        
         cmdLine.println( F("Login site WEB : desactive OFF ! "));
         conf.loginWEB = 0;
     }
  else cmdLine.println( F("Commande syntaxe ERROR ")); 

}

/*************************************************************************************************************
* Sous Programme : LoginPasswordWEB
*=============================================================================================================*
* Fonction   : Modifie le Login:Password de l'authentification du site WEB (encode en Base64)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- chaine Base64 (ex: "dXNlcjpwYXNz" = "user:pass")
*                                  Generateur : https://www.base64encode.org/
* Sortie     : cmdLine.println  -- confirmation de la nouvelle valeur
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.loginPasswordWEB[40]  -- chaine Base64 du login:password WEB (max 39 caracteres + '')
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : login-passweb <chaineBase64>
*************************************************************************************************************/
void LoginPasswordWEB(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  for (size_t i = 0 ; i < sizeof(conf.loginPasswordWEB) ; ++i) {
    conf.loginPasswordWEB[i] = arg[i];
  }
cmdLine.print( F("Nouveau Login:Password (en Base 64 crypte ) OK ! : "));        cmdLine.println( conf.loginPasswordWEB);
}

/*************************************************************************************************************
* Sous Programme : SetMac
*=============================================================================================================*
* Fonction   : Modifie l'adresse MAC de l'Arduino
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- adresse MAC au format "DE:AD:BE:EF:FE:ED" ou "DE-AD-BE-EF-FE-ED"
* Sortie     : cmdLine.println  -- confirmation
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.mac[6]  -- tableau de 6 octets de l'adresse MAC
*-------------------------------------------------------------------------------------------------------------
* Attention  : Chaque Arduino sur le meme reseau doit avoir une adresse MAC unique
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : mac <DE:AD:BE:EF:FE:ED>
*************************************************************************************************************/
void SetMac(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  // Parse MAC address
  // The arg MUST be a valid MAC address (12 hexadecimal digits separated by ':' or '-')
  // i.e. DE:AD:BE:EF:FE:ED or DE-AD-BE-EF-FE-ED
  for (int i = 0; i < 6; ++i) {
    conf.mac[i] = parseByte(arg);
    arg += 3;
  }
  cmdLine.println( F("Commande confirmee : OK ! "));
//  Ethernet_set_macaddr(STATION_IF, &conf.mac[0]);
//    cmdLine.print( F("Nouvel adresse MAC : "));    Serial.println(Ethernet.MACAddress());   
}
 
/*************************************************************************************************************
* Sous Programme : SetDhcp
*=============================================================================================================*
* Fonction   : Active ou desactive le mode DHCP de la configuration reseau
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "ON" / "on"  ou  "OFF" / "off"
* Sortie     : cmdLine.println  -- confirmation dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.dhcp : 0 = IP fixe (statique) / 1 = IP dynamique (DHCP)
*-------------------------------------------------------------------------------------------------------------
* Remarque   : Le changement n'est effectif qu'apres un save + reset
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : dhcp on / dhcp off
*************************************************************************************************************/
void SetDhcp(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));
  
  if ( strcmp (arg, "ON") == 0 || strcmp (arg, "on") == 0 )                                        // strcmp compare deux chaine de caractere http://manpagesfr.free.fr/man/man3/strcmp.3.html 
     {                                                                                             // http://koor.fr/C/cstring/strcmp.wp
        cmdLine.println( F("DHCP : active OK "));
        conf.dhcp = 1;
     }

  else if ( strcmp (arg, "OFF") == 0 || strcmp (arg, "off") == 0 )
     {        
        
         cmdLine.println( F("DHCP : desactive OFF ! "));
         conf.dhcp = 0;
     }
  else cmdLine.println( F("Commande syntaxe ERROR ")); 
}

/*************************************************************************************************************
* Sous Programme : SetIp
*=============================================================================================================*
* Fonction   : Modifie l'adresse IP statique de l'Arduino
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- adresse IP au format "xxx.xxx.xxx.xxx" (ex: "192.168.0.66")
* Sortie     : cmdLine.println  -- confirmation avec l'IP enregistree
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.ip[4]  -- tableau de 4 octets de l'adresse IP
*-------------------------------------------------------------------------------------------------------------
* Remarque   : Le changement n'est effectif qu'apres un save + reset
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : ip <xxx.xxx.xxx.xxx>   /   IP <xxx.xxx.xxx.xxx>
*************************************************************************************************************/
void SetIp(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  // Parse IP address The arg MUST be a valid IP address
  IPAddress ip;
  ip.fromString(arg);
  for (int i = 0; i < 4; ++i) {
    conf.ip[i] = ip[i];
  }
    cmdLine.print( F("Commande confirmee : IP "));          cmdLine.println( IPAddress(conf.ip));
}

/*************************************************************************************************************
* Sous Programme : SetPort
*=============================================================================================================*
* Fonction   : Modifie le port d'ecoute du serveur WEB
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- numero de port en chaine (valeur entre 1 et 65535)
* Sortie     : cmdLine.println  -- confirmation du port enregistre
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.port (uint16_t)  -- port du serveur WEB (defaut : 80)
*-------------------------------------------------------------------------------------------------------------
* Validation : 1 <= port <= 65535 ; sinon message d'erreur
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : port <numero>
*************************************************************************************************************/
void SetPort(const char *arg) {
 if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
 if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
 //cmdLine.print( F("arg "));  Serial.println(arg);
 long port = atol(arg);                                                // atoi Convertie chaine de caractere en nombre
 //cmdLine.print( F("port "));  Serial.println(port);
 
 if ( (port < 65536) && (port > 0) )
    {
      conf.port = port;                                              
    }
 else cmdLine.println( F("ERREUR valeur "));
 
    cmdLine.print( F("Commande confirmee : Port du serveur WEB : ")); cmdLine.println( conf.port);
}

/*************************************************************************************************************
* Sous Programme : Setsubnet
*=============================================================================================================*
* Fonction   : Modifie le masque de sous-reseau (subnet mask)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- masque au format "xxx.xxx.xxx.xxx" (ex: "255.255.255.0")
* Sortie     : cmdLine.println  -- confirmation avec le masque enregistre
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.subnet[4]  -- tableau de 4 octets du masque de sous-reseau
*-------------------------------------------------------------------------------------------------------------
* Remarque   : Le changement n'est effectif qu'apres un save + reset
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : subnet <xxx.xxx.xxx.xxx>
*************************************************************************************************************/
void Setsubnet(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  // Parse IP address The arg MUST be a valid IP address
  IPAddress subnet;
  subnet.fromString(arg);
  for (int i = 0; i < 4; ++i) {
    conf.subnet[i] = subnet[i]; 
  }
  cmdLine.print( F("Commande confirmee : Masque de sous reseau "));   cmdLine.println( IPAddress(conf.subnet));
}
/*************************************************************************************************************
* Sous Programme : Setgateway
*=============================================================================================================*
* Fonction   : Modifie l'adresse de la passerelle (gateway) reseau
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- adresse IP au format "xxx.xxx.xxx.xxx" (ex: "192.168.0.254")
* Sortie     : cmdLine.println  -- confirmation avec la passerelle enregistree
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf.gateway[4]  -- tableau de 4 octets de la passerelle
*-------------------------------------------------------------------------------------------------------------
* Remarque   : Le changement n'est effectif qu'apres un save + reset
*-------------------------------------------------------------------------------------------------------------
* Commandes declenchantes : gateway / passerelle / pass <xxx.xxx.xxx.xxx>
*************************************************************************************************************/
void Setgateway(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  // Parse IP address The arg MUST be a valid IP address
  IPAddress gateway;
  gateway.fromString(arg);
  for (int i = 0; i < 4; ++i) {
    conf.gateway[i] = gateway[i];
  }
  cmdLine.print( F("Commande confirmee : Passerelle "));              cmdLine.println( IPAddress(conf.gateway));
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////            Gestion Sauvegarde -- Reset Debug                                                                             ///////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*************************************************************************************************************
* Sous Programme : SaveConfEEPROM
*=============================================================================================================*
* Fonction   : Sauvegarde la structure conf (configuration complete) dans l'EEPROM
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : cmdLine.println  -- confirmation "EEPROM Save : OK !"
*              EEPROM @ adressBase  -- ecriture de sizeof(conf_t) octets
*-------------------------------------------------------------------------------------------------------------
* Variables lues :
*   conf (conf_t)     -- structure complete de la configuration courante
*   adressBase        -- adresse de debut en EEPROM (uint8_t, defaut : 5)
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : save
*************************************************************************************************************/
void SaveConfEEPROM(const char *arg) { (void)arg;  // arg non utilise (annul warning)
  //if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  // Put configuration to EEPROM (EEPROM address: adressBase)
  EEPROM.put(adressBase, conf);
//  EEPROM.commit();
  delay(200);
  cmdLine.println( F("EEPROM Save : ......... OK ! "));
}
 
/*************************************************************************************************************
* Sous Programme : LoadConfEEPROM
*=============================================================================================================*
* Fonction   : Charge la configuration depuis l'EEPROM dans la structure conf
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : cmdLine.println  -- confirmation ou message d'erreur
*              conf (conf_t)   -- mise a jour depuis l'EEPROM
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf (conf_t)  -- structure remplie depuis l'EEPROM @ adressBase
*-------------------------------------------------------------------------------------------------------------
* Algorithme :
*   1. EEPROM.get(adressBase, conf)
*   2. Si conf.validity != VALID : appel SetDefaultConf + SaveConfEEPROM (reinitialisation)
*   3. Sinon : confirmation "EEPROM load OK"
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : reload
*************************************************************************************************************/
void LoadConfEEPROM(const char *arg) { (void)arg;  // arg non utilise (annul warning)
  //if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  // Get configuration from EEPROM (EEPROM address: adressBase)
  EEPROM.get(adressBase, conf);
 
  // Check EEPROM values validity
  if (conf.validity != VALID) {
       // Set default configuration
       cmdLine.println( F("Invalid EEPROM : utilisation de la configuration par defaut (using default configuration)"));
       SetDefaultConf(nullptr);
       SaveConfEEPROM(nullptr);
 //      EEPROM.commit();
       cmdLine.println( F("Restauration : OK !"));
  }
 else
  {
       cmdLine.println( F("EEPROM load : .......... OK !")); 
//       cmdLine.println( F("Status de l'EEPROM :"));
//       PrintConf(1);                                  // Affiche toutes les infos Reseau
  }
  
}
/*************************************************************************************************************
* Sous Programme : SetDefaultConf
*=============================================================================================================*
* Fonction   : Recharge la configuration usine (default_conf) dans conf et sauvegarde en EEPROM
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : cmdLine.println  -- message de restauration
*              conf (conf_t)   -- remise aux valeurs par defaut
*              EEPROM          -- sauvegarde automatique apres restauration
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   conf (conf_t)  -- copie de default_conf par memcpy
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : defaults
*************************************************************************************************************/
void SetDefaultConf(const char *arg) { (void)arg;  // arg non utilise (annul warning)
  //if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
  // Copy default configuration to the current configuration
  memcpy(&conf, &default_conf, sizeof(conf_t));
  cmdLine.println( F("Restauration de la Configuration de base suite a une corruption des DATA de l'EEPROM "));
  SaveConfEEPROM(nullptr);
  PrintConf(nullptr);
}
/*************************************************************************************************************
* Sous Programme : LoadConf
*=============================================================================================================*
* Fonction   : Charge une configuration depuis un fichier binaire sur la carte SD
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nom du fichier de configuration sur la SD
* Sortie     : cmdLine.println  -- confirmation ou message d'erreur
*              conf (conf_t)   -- mise a jour depuis le fichier
*              EEPROM          -- sauvegarde automatique apres chargement valide
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   fileConf     : objet FsFile pour la lecture (O_RDONLY)
*   tempon[]     : buffer de taille sizeof(conf) pour la lecture brute
*   conf_TMP     : structure temporaire pour verification de validite avant copie dans conf
*-------------------------------------------------------------------------------------------------------------
* Algorithme :
*   1. Ouvre le fichier en lecture
*   2. Lit sizeof(conf) octets dans tempon
*   3. Copie dans conf_TMP et verifie conf_TMP.validity == VALID
*   4. Verifie sizeof(conf_TMP) == sizeof(conf)
*   5. Si valide : copie conf_TMP -> conf et sauvegarde en EEPROM
*   6. Sinon : message d'erreur, configuration non chargee
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : loadconf <NomFichier>
*************************************************************************************************************/
void LoadConf(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; } 
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}
  cmdLine.print( F("Commande confirmee : "));                                                      //  cmdLine.print( F("argument : "));Serial.println(arg);

//  char nameFichier[41] = {0};
//  strcpy (nameFichier, "/");
//  strcat (nameFichier, arg);

FsFile  fileConf;                                                                                 // Ouvre le fichier en lecture seule
    if (!fileConf.open(arg, O_RDONLY)) {
        cmdLine.println( F("Impossible de lire le fichier en lecture"));
        return;
    }
                                                                                                  // https://projetsdiy.fr/esp8266-lire-ecrire-modifier-fichiers-spiffs-librairie-fs-2/      
                                                                                                  // https://github.com/lorol/LITTLEFS/issues/11
                                                                                                  // https://openclassrooms.com/forum/sujet/qu-est-ce-que-unsigned-char-i
  char tempon[sizeof(conf)];
  for (size_t i = 0 ; i < sizeof(conf) ; i++) {
    tempon[i] = fileConf.read();
    //cmdLine.print( tempon[i]);
  }           

  memcpy(&conf_TMP, &tempon, sizeof(conf_TMP) );                                                     //memcpy(&conf, &tempon, sizeof(conf));  
  if ( (conf_TMP.validity != VALID ) || ( sizeof(conf_TMP) != sizeof(conf) ) ) {
  //  if ( ( sizeof(conf_TMP) != sizeof(conf) ) ) {
       cmdLine.println( F("ERROR !! Mismatch : Le fichier de configuration n'est pas du meme format que la version actuelle du programme"));
       cmdLine.println( F("La configuration n'est pas charge"));
       return;
  }
  memcpy(&conf, &conf_TMP, sizeof(conf) );

  EEPROM.put(adressBase, conf);
  fileConf.close();

  cmdLine.print( F("Fichier lu avec la configuration Reseau : "));     cmdLine.println( arg);
  cmdLine.println( F("Faire les commandes save puis reset pour les prendre en compte et redemarrer")); 
}

/*************************************************************************************************************
* Sous Programme : SaveConf
*=============================================================================================================*
* Fonction   : Sauvegarde la configuration courante dans un fichier binaire sur la carte SD
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- nom du fichier de destination sur la SD
* Sortie     : cmdLine.println  -- confirmation de sauvegarde
*              Fichier cree sur SD avec le contenu brut de conf (sizeof(conf_t) octets)
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   fileConf     : objet FsFile pour l'ecriture (O_WRONLY | O_CREAT | O_TRUNC)
*   tempon[]     : buffer de copie de conf pour ecriture octet par octet
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : SaveConf <NomFichier>
*************************************************************************************************************/
void SaveConf(const char *arg) {
  if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; } 
  if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}  
  cmdLine.print( F("Commande confirmee : "));                                       //  cmdLine.print( F("argument : "));Serial.println(arg);

//  char nameFichier[41] = {0};
//  strcpy (nameFichier, "/");
//  strcat (nameFichier, arg);

  FsFile fileConf = sd.open(arg, O_WRONLY | O_CREAT | O_TRUNC);

        if (!fileConf)
          {
              cmdLine.println( F("Impossible de cree le fichier en ecriture."));
              return;
          }
     
        char tempon[sizeof(conf)];
        memcpy(&tempon, &conf, sizeof(conf)); 

        for (size_t i = 0 ; i < sizeof(conf) ; i++) 
           {
               fileConf.write( tempon[i] );
               //cmdLine.print( tempon[i]);
           }  
        fileConf.close();

  
  cmdLine.print( F("Fichier cree avec la configuration Reseau sauvegarde : "));     cmdLine.println( arg);   
}
/*************************************************************************************************************
* Sous Programme : CLIecho
*=============================================================================================================*
* Fonction   : Active ou desactive l'echo du terminal CLI (utile selon le client Telnet utilise)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- "ON" / "on"  ou  "OFF" / "off"
* Sortie     : cmdLine.println  -- confirmation dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables modifiees :
*   cliEcho (boolean) : true = echo actif (PuTTY) / false = echo desactive (MobaXterm)
*-------------------------------------------------------------------------------------------------------------
* Remarque   : MobaXterm gere son propre echo -> desactiver pour eviter le doublement des caracteres
*              PuTTY ne gere pas l'echo -> activer
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : CLI on / CLI off
*************************************************************************************************************/
void CLIecho(const char *arg) {
   if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; }  
   if (arg == NULL || arg[0] == '\0') {cmdLine.println( F("Operande absent : commande annulee")); cmdLine.println();  return;}

  cmdLine.print( F("Commande confirmee : "));                                                      //  cmdLine.print( F("argument : "));Serial.println(arg);

  if ( strcmp (arg, "ON") == 0 || strcmp (arg, "on") == 0 )                                        // strcmp compare deux chaine de caractere http://manpagesfr.free.fr/man/man3/strcmp.3.html 
     {                                                                                             // http://koor.fr/C/cstring/strcmp.wp
        cliEcho = true;
        cmdLine.println( F("CLI echo ON : PuTTY Telnet - remet l'echo normal"));
     }

  else if ( strcmp (arg, "OFF") == 0 || strcmp (arg, "off") == 0 )
     {        
         cliEcho = false;
         cmdLine.println( F("CLI echo OFF : MobaXterm - evite le doublement des caracteres"));
     }
  else cmdLine.println( F("Commande syntaxe ERROR "));     
}
/*************************************************************************************************************
* Sous Programme : ResetNow
*=============================================================================================================*
* Fonction   : Redemarrage immediat de l'Arduino (NVIC_SystemReset)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : Reboot de l'Arduino UNO R4 Minima
*              clientTelnet.println -- avertissement de deconnexion Telnet avant reboot
*-------------------------------------------------------------------------------------------------------------
* Remarque   : La connexion Telnet est coupee au reboot. Reconnecter apres quelques secondes.
*              La boucle while(1) apres NVIC_SystemReset est une securite de blocage (ne doit jamais
*              etre atteinte).
*-------------------------------------------------------------------------------------------------------------
* Commandes declenchantes : reset / restart / reboot
*************************************************************************************************************/
void ResetNow(const char *arg) { (void)arg;  // arg non utilise (annul warning)
  //if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; } 
  cmdLine.print( F("Reboot Now ....."));
  clientTelnet.println(F(" Attention la connexion Telnet va etre coupe !!!!"));
  clientTelnet.println(F(" Vous devez vous reconnecter Mais cela peut prendre du temps (connexion Ethernet de l'Arduino R4) "));
  delay(200);

   NVIC_SystemReset();

  while(1)
  {
     cmdLine.print( F("."));
  }
} 
/*************************************************************************************************************
* Sous Programme : Version
*=============================================================================================================*
* Fonction   : Affiche les informations de version du programme (IDE, date de compilation, auteur)
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : cmdLine.println  -- informations de version dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Informations affichees :
*   - Version IDE Arduino (2.3.7)
*   - Numero de version Kernel (VALID)
*   - Nom du fichier source .ino
*   - Date et heure de compilation (__DATE__ / __TIME__)
*   - Auteur et contact
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : version
*************************************************************************************************************/
void Version(const char *arg) { (void)arg;  // arg non utilise (annul warning)
  //if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; } 
  InfoVersion();
  cmdLine.println( F("Programmation IDE Arduino 2.3.7"));
  cmdLine.print(   F("Version Kernel : "));   cmdLine.println( VALID);

  // Affichage du Nom du Programme source (ino) :                                                               // https://forum.arduino.cc/t/filename-ino-as-string-variable/583738/20
  ino = (ino.substring((ino.indexOf(".")), (ino.lastIndexOf("\\")) + 1));
  cmdLine.print( F("Nom du Programme source ino  : "));   cmdLine.println( ino);      // Print le nom du Fichier ino
  cmdLine.print( F("Date et heure de compilation : "));   cmdLine.print( date_ino);   cmdLine.print( F(" : "));  cmdLine.println( time_ino);
  
  cmdLine.println( F("Saint-Jean Colmar 68000 France")); 
  cmdLine.println( F("Biechy Jean-Marc 2026"));  
  cmdLine.println( F("radiobiechy@gmail.com"));    
} 


/*************************************************************************************************************
* Sous Programme : RamFree
*=============================================================================================================*
* Fonction   : Affiche la quantite de memoire RAM disponible sur l'Arduino UNO R4
*-------------------------------------------------------------------------------------------------------------
* Entree     : const char *arg  -- non utilise (void)
* Sortie     : cmdLine.println  -- valeur en octets (freeMemory()) dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Variables locales :
*   freeRam (int)  -- resultat de freeMemory() (librairie MemoryFree)
*-------------------------------------------------------------------------------------------------------------
* Commande declenchante : RAM   -- ou appel interne depuis PrintConf
*************************************************************************************************************/
void RamFree(const char *arg) { (void)arg;  // arg non utilise (annul warning)
  //if ( conf.login_cmdTelnetSwitch == 1 )  { cmdLine.println( F("Acces Refuse"));   return; } 
  int freeRam = freeMemory();
  cmdLine.print( F("Memoire RAM disponible de l'arduino : "));   cmdLine.print( freeRam);   cmdLine.println( F(" byte"));
}


/*
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//  Mini Documentation
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
Version du Programme : IoTOS ---
Biechy - JeanMarc
BAC PRO CIEL - BTS // Institution SaintJean 68000 Colmar
radiobiechy@gmail.com


DOC Version - Serial Telnet COLOR
DOC Version - Name system
DOC Version - test IP validity
DOC Version - CmdLine Serial Telnet cmdLine.print(" ");
DOC Version - Partition FatFs datalogger FTP - site WEB switch Login
DOC Version - Serveur WEB -- Javascript AJAX in out ConfIP SSID - CSS Responsive
DOC Version - BUG port - Verification IP
DOC Version - Options - Ping cmdLine

-----------------------------------------------------------------------------------------------------
https://xdevs.com/guide/color_serial/

Formatage de la sortie serie avec des couleurs
L'utilisation de la sortie serie pour les messages de debogage et les journaux de la console est un outil tres frequent pour les developpeurs de micrologiciels et de logiciels. Mais lorsque vous travaillez sur de grands systemes et de grandes sorties de donnees, il n'est souvent pas facile de trouver les donnees necessaires entre de nombreux symboles.

L'utilisation de la couleur est une amelioration facile  obtenir, qui peut contribuer  ameliorer la lisibilite des journaux  l'Åil nu. Ce court article couvre les codes de couleur de base et des exemples d'utilisation. La coloration est prise en charge par de nombreux programmes terminaux, tels que PuTTy, etc.

Definition des couleurs ANSI
Premier plan  Arriere-plan
noir  30  40  
rouge 31  41
vert  32  42
jaune 33  43
bleu  34  44
magenta 35  45
cyan  36  46
blanc 37  47
\033[1m Texte en gras
\033[4m Texte souligne
\033[5m Texte clignotant
\033[7m Texte inverse
Couleurs de premier plan :
Code  ESC -chaine Resultat
\033[0;30;41m ^[0;30;41m  Noir sur blanc
\033[0;31m  ^[0;31m Premier plan rouge
\033[0;32m  ^[0;32m Premier plan vert
\033[0;33m  ^[0;33m Premier plan jaune
\033[0;34m  ^[0;34m Premier plan bleu
\033[0;35m  ^[0;35m Premier plan magenta
\033[0;36m  ^[0;36m Premier plan cyan
\033[0;37m  ^[0;37m Premier plan blanc
\033[0;39m  ^[0;39m Couleur par defaut
Couleurs vives de premier plan :
Code  ESC -chaine Resultat
\033[1;30m  ^[1;30m Bg Noir sur blanc
\033[1;31m  ^[1;31m Bg Premier plan rouge
\033[1;32m  ^[1;32m Premier plan vert Bg
\033[1;33m  ^[1;33m Bg Premier plan jaune
\033[1;34m  ^[1;34m Bg Bleu premier plan
\033[1;35m  ^[1;35m Premier plan bg magenta
\033[1;36m  ^[1;36m Bg Cyan premier plan
\033[1;37m  ^[1;37m Bg Blanc premier plan
\033[1;39m  ^[1;39m Couleur par defaut
Couleurs de fond :
\033[0;40m  ^[0;40m Fond noir
\033[0;41m  ^[0;41m Fond rouge
\033[0;42m  ^[0;42m Fond vert
\033[0;43m  ^[0;43m Fond jaune
\033[0;44m  ^[0;44m Fond bleu
\033[0;45m  ^[0;45m Fond magenta
\033[0;46m  ^[0;46m Fond cyan
\033[0;47m  ^[0;47m Fond blanc
\033[0;49m  ^[0;49m Arriere-plan par defaut
Reinitialiser le code couleur : \033[m

Exemple d'utilisation des couleurs
void  color_debug ( void )  {
     // Affiche du texte noir sur fond vert et revient  la normale 
    printf ( "\033[0;30;41m Affiche du texte noir sur fond vert \r\n \033[0;39;49m" ) ;
} ;
Sortir:

ASCII
https://fr.wikibooks.org/wiki/Les_ASCII_de_0_%C3%A0_127/Caract%C3%A8res_de_contr%C3%B4le


const char PROGMEM page_WEB_index_HTML[] =R"=====(  ....   )====="; 
client.write_P(page_WEB_index_HTML, sizeof(page_WEB_index_HTML)-1);


*/
