Arduino Configuration IP par page WEB + Serial commande en EEPROM

Voici un projet de commande d'une LED par page WEB et de la configuration IP de l'Arduino :

La commande d'un bouton ON / OFF par page WEB .... (très classique)

La seconde page WEB permet de configurer l'IP de l'arduino et de le rebooter dans la nouvelle IP

La troisième fonction : Permettre de configurer l'adresse IP par ligne de commande
(Configuration CLI ou CmdLine c'est une sorte de MSDOS ....)
En mode Liaison USB (émulation série : Port COM compatible avec PUTTY .... ou autre

Fonction du CmdLine:
Commande de LED ON /OFF
Configuration IP
Sauvegarde de la configuration (en EEPROM)
Reboot de l'arduino (et rechagement de la nouvelle configuration IP)

```
[color=blue]

  • Commande de l'Arduino Par Ligne de Commande (CmdLine)
  • IDE --> Outils --> Moniteur série ==> Configurer : 115200 baud et a coté séléctionner "Nouvelle Line" ou "Les deux, NL CR"
  • Ou Utiliser un Logiciel : PUTTY, KUTTY, sscom32E.exe - Termite.exe ....
  • CmdLine --> Taper la commande espace argument : Commande disponible :

help

  • General System commands :
    help           aide commande cmd Serial (man)
    ram            RAM Disponible
    LED on/off     Test LED ON ou OFF

  • Configuration IP commands :
    print          print configuration courrante (info)
    mac      set local MAC address (default: DE:AD:BE:EF:FE:ED
    dhcp on/off    enable/disable DHCP
    ip        set local IP address Exemple : ip 192.168.0.66
    subnet    set Masque de sous réseau
    gateway   set Passerelle
    port <0 65535> set Port WEB (Defaut 80)

  • EEPROM access commands:
    reload         load EEPROM avec la configuration sauvegarde
    defaults       set default configuration HARD Reset (192.168.0.10
    save           save configuration en EEPROM
    reset          Restart Arduino et load la configuration enregistre en EEPROM

Exemple : Pour changer l'IP de l'Arduino : taper commande :

ip 192.168.0.2
puis la commande save et enfin reset pour redemarrer l'arduino dans la nouvelle configuration

[/color]
```

Entete :

/*********************************************************************************************************************
* Prise IP --> Commande LED par page WEB
*
* WEB Serveur Securise (Login mot de passe acces WEB) pour Commande Bouton WEB --> LED Arduino                                        Version Teste OK !!
* Wersion Ethernet
* 
* Page WEB avec Bouton CSS 
* Configuration IP par page WEB
* Configuration IP par Ligne de commande (Serial Commande Line CmdLine)
* Autentification : ON / OFF Login Password site WEB (user pass) et modification de Login:Password : https://www.base64encode.org/ 
/*********************************************************************************************************************
* Version Programme :
* Ethernet v1                                                : ok
* Wifi ESP01 (Arduino UNO + Ethernet Schield)                : X
* Arduino WeMos D1 R1 (ESP8266) (Wifi)                       : X
* 
* Fonction : Commande de Relais (LED) par page WEB
* Commande une LED avec des boutons WEB dessine en CSS       : ok
* Autentification Login Mot de passe (Avec activation)       : off
* Changement de Login Password en Base 64                    : off                                    https://www.base64encode.org/ 
* Configuartion IP par Page WEB                              : ok
* Configuration IP pare Serial Commande (Commande Line)      : ok
* Watchdog                                                   : ok
* 
* Utiliastion des fonctions Char pour les tempons (pas de String) ----> RAM 235O boucle principale // Minimal 187o / 2048
* Utilisation de la fonction sscanf (Attention Bug compilation si sscanf est utilise en sous programme)
* Configuration IP par fonction STRUCT
* Site WEB en variable PROGMEM
* Login et Mot de passe : Autentification Code Exemple :
* https://microcontrollerslab.com/esp32-password-protected-web-server-in-arduino-ide/
/*********************************************************************************************************************
* Commande de l'Arduino Par Ligne de Commande (CmdLine) 
* IDE --> Outils --> Moniteur série ==> Configurer : 115200 baud et a coté séléctionner "Nouvelle Line" ou "Les deux, NL CR"
* Ou Utiliser un Logiciel : PUTTY, KUTTY, sscom32E.exe - Termite.exe ....   
* CmdLine --> Taper la commande espace argument : Commande disponible :
> help

- General System commands :
    help           aide commande cmd Serial (man)
    ram            RAM Disponible
    LED on/off     Test LED ON ou OFF

- Configuration IP commands :
    print          print configuration courrante (info)
    mac <MAC>      set local MAC address (default: DE:AD:BE:EF:FE:ED
    dhcp on/off    enable/disable DHCP
    ip <IP>        set local IP address Exemple : ip 192.168.0.66
    subnet <IP>    set Masque de sous réseau
    gateway <IP>   set Passerelle
    port <0 65535> set Port WEB (Defaut 80)

- EEPROM access commands:
    reload         load EEPROM avec la configuration sauvegarde
    defaults       set default configuration HARD Reset (192.168.0.10
    save           save configuration en EEPROM
    reset          Restart Arduino et load la configuration enregistre en EEPROM

 Exemple : Pour changer l'IP de l'Arduino : taper commande :
 > ip 192.168.0.2
 puis la commande save et enfin reset pour redemarrer l'arduino dans la nouvelle configuration 
> 
/*********************************************************************************************************************
* -------------------------------------------->    Sortie :
* Module LED sur PORT Digital D2
* http://wiki.seeedstudio.com/Grove-Red_LED/
* http://wiki.seeedstudio.com/Grove-LED_Socket_Kit/
* 
* -------------------------------------------->    Entree / Sortie --> Transfert
* Ethernet shield v1 - Wiznet W5100 utilise les port SS 10, MOSI 11, MISO 12, SCL 13 -- SD card 4
* http://arduino.cc/en/Main/arduinoBoardEthernet
* http://arduino.cc/en/Reference/Ethernet
* https://www.arduino.cc/en/Main/ArduinoEthernetShieldV1
* Attention : verifier la verson de shiel Ethernet : il existe deux verson V1 et V2 !!!!
/*********************************************************************************************************************
* Librairie : 
* Arduino Serial Commande Line (CmdLine) 
* https://github.com/IndustrialShields/arduino-CmdLine
/*********************************************************************************************************************
* Auteur   : Saint-Jean Colmar             * Arduino UNO et Shield Base Grove               * Date  : 1/9/2019
* Compilateur teste sur version : 1.65 - arduino-1.8.5-windows.exe  (Attention les version 1.8.9 1.8.11 compile en dépassant la memoir : il faut enlever des commantaire)
/*********************************************************************************************************************

Déclaration des variables :

/****** Déclarations / Librairies / Variables Globales ******/
#include <SPI.h>                                                        // Libairie LOCAL de l'IDE d'Arduino
#include <Ethernet.h>                                                   // Libairie LOCAL de l'IDE d'Arduino
#include <avr/wdt.h>                                                    // Libairie LOCAL de l'IDE d'Arduino : Pour la fonction Reboot et Watchdog
#include <EEPROM.h>                                                     // Libairie LOCAL de l'IDE d'Arduino :Librairie eemprom 

// Libraire Serial Commande Line CmdLine
#include <CmdLine.h> 
CmdLine cmdLine(Serial);

// Declaration PORT Entrer Sortie de l'Arduino 
#define pinLED 2

// Autre Constante
#define VALID  11                                      // ID Cette variable permet de verifier que l'EEPROM a deja ete programme par ce programme Valeur de test pour savoir si des donnes sont valid en EEPROM (de type Adresse IP)


// Parametre de la carte Reseau                             
//byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };  // Adress Physique de la carte Reseau MAC physical mac address
const byte DNS[] = { 1, 1, 1, 1 };                      // DNS

typedef struct {                                        // https://www.squalenet.net/fr/ti/tutorial_c/14-structures-unions.php5
  uint8_t validity;                                     // ID Cette variable permet de verifier que l'EEPROM a deja ete programme par ce programme Valeur de test pour savoir si des donnes sont valid en EEPROM (de type Adresse IP)
  boolean led;                                          // Etat de la LED
  
  uint8_t mac[6];                                       // local MAC address
  boolean dhcp;                                         // Arduino configure en IP STATC = 0 // Arduino configure en IP DHCP = 1 
  uint8_t ip[4];                                        // local IP address
  uint8_t subnet[4];  
  uint8_t gateway[4];
  uint16_t port;                                        // Port serveur WEB
} conf_t;
conf_t conf;                                            // conf est la variable structure avec les information conf.ip[] , conf.subnet[] ....

// Définir la variable de configuration par défaut STRUCT  Definition des valeurs Reseau par defaut
const conf_t default_conf = {
  VALID,                                                // ID Cette variable permet de verifier que l'EEPROM a deja ete programme par ce programme 
  0,
  {0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED},                 // mac
  0,                                                    // dhcp
  { 192, 168, 0, 10},                                   // ip
  { 255, 255, 255, 0 },                                 // Masque sous reseau
  { 192, 168, 0, 254 },                                 // Passeralle
  80,                                                   // PORT WEB 0-- 65536 Par defaut port serveur WEB : 80
};

// Variable Moteur Serveur WEB
#define REQ_BUF_SZ   250                                // taille de la mémoire tampon utilisée pour capturer les requêtes HTTP
char HTTP_req[REQ_BUF_SZ] = {0};                        // buffered HTTP request stored as null terminated string
unsigned char req_index = 0;                            // index into HTTP_req buffer
char c = 0;                                             // caractere recu pas le client

EthernetServer server(80);                              // PORT du serveur server port  

// Declaration Valeurs programme (Etiquette = Valeur) 
unsigned char adressBase = 100;                         // Adresse de base de depart de l'ecriture des DATA en EEPROM
boolean compteur = 0;                                   // Compteur pour ne pas executer la fonction RESET lors du premier acces a la page WEB de configuration IP

/*********************************************************************************************************************/
// Fichier incut en .h (Sous Programme) 

#include "index_HTML_PROGMEM.h"                         // Site WEB de bas :Commande Bouton WEB --> LED ARduino
#include "ConfIP_HTML_PROGMEM.h"                        // Page HTML de configuration IP 
#include "ConfigIP_Serial_CmdLine.h"                    // Commande Line (cmd) Serial de configuration IP 
/*********************************************************************************************************************/
void SerialBarr()
{
    Serial.println(F("-------------------------------------------------------"));
}

Setup :

/*********************************************************************************************************************/
/**************  Initialisation                                                                       ****************/
/*********************************************************************************************************************/
void setup() {
// Initialisation Liaison série Arduino --> USB --> PC : Pour DEBUG 
   Serial.begin(115200);
   delay(100);
   Serial.println();  Serial.println();  Serial.println();  Serial.println();
   Serial.println(F("**************************************************"));
   Serial.println(F("* Programme : Serveur WEB Commande Bouton WEB    *"));
   Serial.println(F("* Prise IP      Login Autentification (ON/OFF)   *"));
   Serial.println(F("*               Configuation IP WEB Page         *"));
   Serial.println(F("*               Configuation IP Serial CmdLine   *"));   
   Serial.println(F("**************************************************"));
   Serial.println();
   
// RAM disponible au demarrage
  Serial.println(F("Arduino Setup START : "));
  ramFree(nullptr);
  Serial.println();


// Load EEPROM configuration
  loadConf(nullptr);
  Serial.println();
  
// Configuration PORT IO Arduino   
  pinMode(pinLED, OUTPUT);
  digitalWrite(pinLED, conf.led);                                                                  // Charge letat de la LED
    
// Initialisation du Shield Ethernet v1 en DHCP ou IP Fixe
  if (conf.dhcp == 0) 
    { 
      Serial.println(F("DHCP : OFF --> Configuration : IP Fixe"));     Serial.println();
      Ethernet.begin(conf.mac, conf.ip, DNS, conf.gateway, conf.subnet); 
    }
  else
    { 
      Serial.println(F("DHCP : ON --> Configuration en IP Automatique "));       Serial.println();
      Ethernet.begin(conf.mac); 
    }
// Test Informations carte Reseau
  switch (Ethernet.maintain())
  {
    case 1: Serial.println(F("Error: renewed fail")); break;
    case 2: Serial.println(F("Renewed success")); break;
    case 3: Serial.println(F("(Error: rebind fail")); break;
    case 4: Serial.println(F("Rebind success")); break;
    default: Serial.println(F("TEST : Carte Reseau : OK !")); break;                                //nothing happened  
  }
  
  SerialBarr(); // "-------------------------------------------------------
  Serial.println(F("Lecture des Infos de la carte Reseau : "));
  printConf(nullptr);                                                                              // N'affiche que la configuration Reseau de la carte reseau nullptr
  Serial.println();
  SerialBarr(); // "-------------------------------------------------------
  
// Demmarage du serveur WEB
  server = EthernetServer(conf.port);                                                               // Change le port du serveur
  server.begin();
  Serial.print(F("Serveur WEB START port : "));    Serial.print(conf.port);    Serial.println(F("  ...... OK ! "));

// Start Serial Commande Line (CmdLine)
  SerialBarr(); // "-------------------------------------------------------
  Serial.println(F("Serial Commande Line (CmdLine) START : OK !"));
  Serial.println(F(" Taper help pour voir les commandes disponibles"));
  Serial.println(F(" Taper print pour voir la configuration réseau"));
  Serial.println();
  cmdLine.begin(commands, sizeof(commands));  

// Initialisation Watchdog
  wdt_enable (WDTO_8S);  // réinitialiser après 8 secondes, si aucun rearmement : wdt_reset(); n'est reçu

}

Programme principale :

/*********************************************************************************************************************/
/**************  Programme Principal                                                                  ****************/
/*********************************************************************************************************************/
void loop(){
// Update CmdLine library
  cmdLine.update();

  
EthernetClient client = server.available();                                                   // écouter les clients entrants (Create a client connection)


 if (client) {                                                                                // client obtenu?
  Serial.println();  Serial.println(F("*****  Nouveau Client connecte :"));                   // Si un nouveau client se connecte,imprimer un message sur le port série

        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {                                                         // données client disponibles pour lire lire 1 octet (caractère) du clientla dernière ligne de la demande du client est vide et se termine par \ n 
                c = client.read();                                                            // répondre au client seulement après la dernière ligne reçue  Tamponner la première partie de la requête HTTP dans le tableau HTTP_req (chaîne)                                                                    
                                                                                              // laisse le dernier élément dans le tableau de 0 à null termine la chaîne (REQ_BUF_SZ - 1)
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c;                                                  // enregistrer le caractère de requête HTTP
                    req_index++;
                }               
                //Serial.print(c);                                                            // Impression du caractère de requête HTTP sur le moniteur série                                                                      
                if (c == '\n' && currentLineIsBlank) {                                        // la dernière ligne de la requête client est vide et se termine par \ n
                                                                                              // répond au client seulement après la dernière ligne reçue// open requested web page file

                    for (int i = 0 ; i < req_index ; i++) {   Serial.print(HTTP_req[i]); }    // Impression du caractère de requête HTTP sur le moniteur série 
                                       
                    // Envoie Page WEB index.html ou confIP.html
                    PageWEB(client);

                    delay(10);

                    // réinitialiser l'index du tampon et tous les éléments du tampon à 0
                    req_index = 0;
                    StrClear(HTTP_req, REQ_BUF_SZ);
                    //Serial.println(F"Reset HTTP_req"));
                    break;
                } // end if (c == '\n' && currentLineIsBlank)
                
                
                if (c == '\n') {                                                                // chaque ligne de texte reçue du client se termine par \r \n
                                                                                                // dernier caractère en ligne du texte reçu Commencer une nouvelle ligne avec le prochain caractère lu
                  currentLineIsBlank = true;
                } 
                else if (c != '\r') {                                                           // un caractère de texte a été reçu du client
                  currentLineIsBlank = false;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(10);                                                                              // donnez au navigateur le temps de recevoir les données
        client.stop();                                                                          // close the connection
        Serial.println(F("*****  Client Deconnecte (Fin boucle)"));
        SerialBarr(); // "-------------------------------------------------------
        Serial.println();
        Serial.println(F("> "));
    } // end if (client)

// Watchdog
wdt_reset();    // Reset du Watchdog
}
/*************************************************************************************************************/
/**************  Sous Programmes                                                              ****************/
/*************************************************************************************************************/

// met tous les éléments de str à 0 (efface le tableau)
void StrClear(char *str, char length)
{
    for (int i = 0; i < length; i++) {
        str[i] = 0;
    }
}

/*********************************************************************************************************************
* Sous Programme
* Fonction : recherche la chaîne sfind dans la chaîne str
/*********************************************************************************************************************
* Entree : Chaine de caractere (tableau) , chaine de caractere a trouver
* 
* Sortie : 
* renvoie 1 si la chaîne a été trouvée
* renvoie 0 si la chaîne n'est pas trouvée
/*********************************************************************************************************************/
char StrContains(char *str, char *sfind)
{
//  Serial.println(F("Start StrContains "));
    unsigned char found = 0;
    unsigned char index = 0;
    unsigned char len;

    len = strlen(str);
    
    if (strlen(sfind) > len) {                              // longueur de la chaine http://koor.fr/C/cstring/strlen.wp
        return 0;
    }
    while (index < len) {
        if (str[index] == sfind[found]) {                    // Compare caracter par caratere les deux cahines
            found++;
            if (strlen(sfind) == found) {
                return 1;
            }
        }
        else {
            found = 0;
        }
        index++;
    }

    return 0;
}

Fonction :

/*********************************************************************************************************************
* Sous Programme
* Fonction : Envoie une page WEB 
* utilise la sous prog envoiPageWEB (pour envoyer avec tempon le site en utilisant les variables PROGMEM)
/*********************************************************************************************************************
* Entree : (client de EtherntClient)
* 
* Sortie : Envoie des DATA vers le navigateur WEB
/*********************************************************************************************************************/
void PageWEB(EthernetClient &client)
{
Serial.println(); Serial.println();
// Page HTML : ConfPage.html (Page 2)*************************************************************************************************
  if (StrContains(HTTP_req ,"GET /ConfPage.html") ) {                                       
        Serial.println(F("Envoie Page WEB : ConfigIP.html ")); 
        ramFree(nullptr);
        
        // envoiPageWEB ( nom du client, Nom de la chaine PROGMEM, taille de la chaine PROGMEM );
        EnvoiPageWEB(client, page_WEB_ConfigIP , strlen_P(page_WEB_ConfigIP));

        // Exemple d'envoie du navigateur sur la liaison serie
        /*  Envoie de la page WEB
            http://192.168.0.190/ConfPage.html?ST=0&IP0=192&IP1=168&IP2=0&IP3=15&MSR0=255&MSR1=255&MSR2=255&MSR3=0&GAT0=192&GAT1=168&GAT2=0&GAT3=254
        // Recu pr la liason serie
            GET /ConfPage.html?ST=0&IP0=192&IP1=168&IP2=0&IP3=15&MSR0=255&MSR1=255&MSR2=255&MSR3=0&GAT0=192&GAT1=168&GAT2=0&GAT3=254 HTTP/1.1
            Host: 192.168.0.14
            Connection: keep-alive
            Accept: text/html,ap
        */
        // Fonction sscanf permet de tranformer le tableau de char avec les infos de la conf ip et les mettre dans la structure conf.IP conf.gateway ....                          
             // https://www.tutorialspoint.com/c_standard_library/c_function_sscanf.htm          // http://koor.fr/C/cstdio/fscanf.wp  //https://en.cppreference.com/w/c/io/fscanf
             // Fonction scanf qui decoupe la chaine de caractere en fonction de delimiteur      // https://stackoverflow.com/questions/14146434/using-scanf-for-reading-an-unsigned-char
        //sscanf(HTTP_req, "GET /ConfPage.html?ST=%hhu&IP0=%hhu&IP1=%hhu&IP2=%hhu&IP3=%hhu&MSR0=%hhu&MSR1=%hhu&MSR2=%hhu&MSR3=%hhu&GAT0=%hhu&GAT1=%hhu&GAT2=%hhu&GAT3=%hhu", &conf.dhcp, &conf.ip[0], &conf.ip[1], &conf.ip[2], &conf.ip[3], &conf.subnet[0], &conf.subnet[1], &conf.subnet[2], &conf.subnet[3], &conf.gateway[0], &conf.gateway[1], &conf.gateway[2], &conf.gateway[3] ); 

                                                 
        // A l'acces a la page WEB de configuration IP par le navigateur celle ci est execute une fois (et donc bug et REST !!!) on met donc un compteur pour ne pas executer le reset la permiere fois 
        //Serial.print("Compteur : "); Serial.println(compteur);
        if (compteur == 1)
            {
                sscanf(HTTP_req, "GET /ConfPage.html?ST=%hhu&IP0=%hhu&IP1=%hhu&IP2=%hhu&IP3=%hhu&MSR0=%hhu&MSR1=%hhu&MSR2=%hhu&MSR3=%hhu&GAT0=%hhu&GAT1=%hhu&GAT2=%hhu&GAT3=%hhu", &conf.dhcp, &conf.ip[0], &conf.ip[1], &conf.ip[2], &conf.ip[3], &conf.subnet[0], &conf.subnet[1], &conf.subnet[2], &conf.subnet[3], &conf.gateway[0], &conf.gateway[1], &conf.gateway[2], &conf.gateway[3] ); 

                delay(1);  
                printConf("1");                    // Affiche toute la fonfiguration reseau
                saveConf(nullptr);
                
                Serial.println(F("Enregistrement de la configuration IP en EEPROM : Ok!")); Serial.println();
                client.print(F("<p> Configuration Enregistre <p> "));
                client.print(F("L'Arduino est accessible dans quelques instant a l'adresse IP :  "));  
                if( conf.dhcp == 0)
                   { 
                       client.print(conf.ip[0],DEC);   client.print(".");     client.print(conf.ip[1],DEC);     client.print(".");     client.print(conf.ip[2],DEC);     client.print(".");     client.print(conf.ip[3],DEC);  
                   }
                else client.print(F("Arduino DHCP OK !"));
                  
                client.print(F("<p> RESET Arduino : Now ............... <p> "));
                Serial.println(F("------- RESET ARDUINO !!!! -------")); Serial.println();
                compteur = 0;
                resetNow(nullptr);
            }

        compteur++; 
        return;                
  } // Fin Page WEB configuration 
 
// Page HTML : index.html*************************************************************************************************
                                 
if (StrContains(HTTP_req, "GET / ") || StrContains(HTTP_req, "GET /index.htm") || StrContains(HTTP_req, "?BoutonON") || StrContains(HTTP_req, "?BoutonOFF") ) {
       Serial.println(F("Envoie Page WEB : index.html")); 
       ramFree(nullptr);
       
       // envoiPageWEB ( nom du cleint, Nom de la chaine PROGMEM, taille de la chaine PROGMEM
       EnvoiPageWEB(client, page_WEB_index_HTML , strlen_P(page_WEB_index_HTML));

        delay(1);
      
       //client.stop();                                                //stopping client
       // Partie Controle des Boutons
       if (StrContains(HTTP_req ,"?BoutonON") ){
            conf.led = 1;
            digitalWrite(pinLED, HIGH);
            Serial.println(F("LED est ON"));
            //client.print(F("<style>p { color: green;}</style>

<p><a>LED est ON</a></p>"));
            client.print(F("

<p><font color=\"green\"><font size=6><strong><a>LED est ON</a></strong></font></p>"));
       }
       if (StrContains(HTTP_req ,"?BoutonOFF") ){
            conf.led = 0;
            digitalWrite(pinLED, LOW);
            Serial.println(F("LED est OFF")); 
            client.print(F("

<p><font color=\"red\"><font size=6><strong><a>LED est OFF</a></strong></font></p>"));
       }
       EEPROM.put(adressBase+1, conf.led);
       //return; 

  } // Fin page HTML
// ***********************************************************************************************************************  
      // ICI on peut placer une autre pages WEB 



      
}

/*********************************************************************************************************************
* Sous Programme
* Fonction : Envoie une page WEB en utilisant les variables PROGMEM
/*********************************************************************************************************************
* Entree : Nom du PROGMEM
* 
* Sortie : Envoie des DATA vers le navigateur WEB
/*********************************************************************************************************************/
void EnvoiPageWEB(EthernetClient &client, const char *str_P, int taille)      
{
  const int tailleTampon = 32;                                                     // taille du tampon d'envoi
  char buffer[tailleTampon];
  int restant = taille;
  const char *aEnvoyer = str_P;                                                     // Charge le nom du PROGMEM reponse_debut ou reponse_fin
  int tailleEnvoi = tailleTampon;

  Serial.println(F("Routine EnvoiPageWEB : ")); 
  ramFree(nullptr);

  while  (restant > 0) {
         if  (tailleEnvoi > restant)
             tailleEnvoi = restant;
             
         memcpy_P(buffer, aEnvoyer, tailleEnvoi);                                    // void *   memcpy_P (void *, const void *, size_t)
         
         if  (client.write((const uint8_t *)buffer, tailleEnvoi) != tailleEnvoi)
             return;                                                                 // erreur
        
        restant -= tailleEnvoi;
        aEnvoyer += tailleEnvoi;
        }
}

Merci !!
Intéressante la gestion en ligne de commande .....

Le sérial commande utilise une librairie : CmdLine

Très bonne librairie :slight_smile:

Le principe est qu’avec putty ou autre on puisse envoyer des commandes pré déterminé

Dans notre cas c’est la configuration IP de l’Arduino

Voici ce que retourne la commande help

[color=blue]
* Commande de l'Arduino Par Ligne de Commande (CmdLine) 
* IDE --> Outils --> Moniteur série ==> Configurer : 115200 baud et a coté séléctionner "Nouvelle Line" ou "Les deux, NL CR"
* Ou Utiliser un Logiciel : PUTTY, KUTTY, sscom32E.exe - Termite.exe ....   
* CmdLine --> Taper la commande espace argument : Commande disponible :
> help

- General System commands :
    help           aide commande cmd Serial (man)
    ram            RAM Disponible
    LED on/off     Test LED ON ou OFF

- Configuration IP commands :
    print          print configuration courrante (info)
    mac <MAC>      set local MAC address (default: DE:AD:BE:EF:FE:ED
    dhcp on/off    enable/disable DHCP
    ip <IP>        set local IP address Exemple : ip 192.168.0.66
    subnet <IP>    set Masque de sous réseau
    gateway <IP>   set Passerelle
    port <0 65535> set Port WEB (Defaut 80)

- EEPROM access commands:
    reload         load EEPROM avec la configuration sauvegarde
    defaults       set default configuration HARD Reset (192.168.0.10
    save           save configuration en EEPROM
    reset          Restart Arduino et load la configuration enregistre en EEPROM

 Exemple : Pour changer l'IP de l'Arduino : taper commande :
 > ip 192.168.0.2
 puis la commande save et enfin reset pour redemarrer l'arduino dans la nouvelle configuration 
> [/color]

Utilisation de la librairie :
Exemple pour afficher la RAM disponible :

Il faut déclarer le sous programme

void ramFree(const char *arg);

puis lui donner un nom de commande

 {"ram", ramFree},

et enfin les sous programme qui sera appelé

```
/*************************************************************************************************************

  • Sous Programme
  • Fonction : Memoire RAM Disponible

  • Entree : X
  • Sortie : Info sur la RAM disponible dans l’Arduino
    **************************************************************************************************************/
    void ramFree(const char *arg)                  // arg envoie une chaine de caracteres
    {

int size = 2048;
 byte *buf;
 while ((buf = (byte *) malloc(–size)) == NULL);
 free(buf);
 Serial.print(F(“Mémoire RAM disponible de l’arduino : “));   Serial.print(size);   Serial.println(F(” / 2048”));

}
```

Par contre je n’arrive pas à vous mettre le code car le forum limite à 9000 caractères :wink:
il et donc joint

ConfigIP_Serial_CmdLine.h (16.1 KB)

je ne connaissais pas cette librairie, il me semble que je vais l'utiliser car elle correspond à une 'façon de faire les choses' qui pour moi n'a pas perdu son intérêt et me convient tout à fait !!
Pour moi le terminal sera par exemple un smartphone sous Android + l'application Serial USB Terminal.

Merci encore pour le signalement de cette librairie et l'application qui en est faite.

Page HTML (Principale Bouton ON/OFF)

On note que on n’utilise un PROGMEM ce qui permet de modifier facilement une page WEB …

```
//
// Variable PROGMEM → Page WEB index.html (Commande Bouton WEB)
/
/
PROGMEM const char page_WEB_index_HTML =
// Entéte de la réponse HTTP     – Attention remplacer :es " par " !!!     et le /n c’est le retour chariot

“HTTP/1.1 200 OK\n”
“Content-Type: text/html\n”
“Connection: close\n”                        // the connection will be closed after completion of the response
//“Refresh: 5”\n                             // refresh the page automatically every 5 sec
“\n”

// début de la page HTML

“\n”
“”
 “”
   "<link rel=“shortcut icon” type=“image/png” href="http://eg.com/favicon.ico"/>"

// CSS
    “”
      “body{margin:60px 0px;”
         “padding:0px;”
         “text-align:center;}”
      “h1 {text-align: center;font-family:Arial, “Trebuchet MS”, Helvetica, sans-serif;}”
      “h2 {text-align: center;font-family:Arial, “Trebuchet MS”, Helvetica, sans-serif;}”

“a{text-decoration:none;”
         “width:75px;height:50px;”
     “border-color:black;”
      “border-top:2px solid;”
      “border-bottom:2px solid;”
      “border-right:2px solid;”
      “border-left:2px solid;”
      “border-radius:10px 10px 10px;”
       “-o-border-radius:10px 10px 10px;”
       “-webkit-border-radius:10px 10px 10px;”
      “font-family:“Trebuchet MS”,Arial, Helvetica, sans-serif;”
       “-moz-border-radius:10px 10px 10px;”
      “background-color:#293F5E;”
      “padding:8px;”
      “text-align:center;}”

“a:link {color:white;}”      
         “/* unvisited link /"
      “a:visited {color:white;}”  
         "/
visited link /"
      “a:hover {color:white;}”
         "/
mouse over link /"
      “a:active {color:white;}”  
         "/
selected link */”
    “\n”
 // FIN CSS
                                                 
   “Arduino WEB\n”
 “\n”
 
 “\n”
   “

Arduino Commande Bouton WEB


   “

   "
"
   “

Selection :

"
   "
"
     “<a href=”/?BoutonON”">Mise a ON LED"
     “<a href=”/?BoutonOFF"">Mise a OFF LED"
   "
"
   "
"
   "
"
   "
"
   “

   "
"
     “


        “<a href=“ConfPage.html”>Configuration IP”
     “


   "
"
   “

   "
"
 “”
“\n”
;
```

La page HTML de configuration (toujours en PROGMEM)

```
[color=blue]
//
// Variable PROGMEM → Page WEB Configuration IP
/
/
PROGMEM const char page_WEB_ConfigIP =
// Entéte de la réponse HTTP     – Attention remplacer : les 'guillement) " par " !!!
“HTTP/1.1 200 OK\n”
“Content-Type: text/html\n”
“Connection: close\n”                                           // la connexion sera fermée après l’achèvement de la réponse
//“Refresh: 5”\n                                                  // actualise la page automatiquement toutes les 5 secondes
“\n”

// début de la page HTML

“\n”
“”
 “”
     "<link rel=“shortcut icon” type=“image/png” href="http://eg.com/favicon.ico">"
     “Arduino Configuration IP \n”
 “”

“”
   “

Arduino Configuration IP


   “

   "
"
   “”
     “”
       “”
         "”
       “”
         ""
         ""
         ""
         ""
         “”
       “”
       “”
         ""
         ""
         ""
         ""
         “”
       “”
       “”  
         ""
         ""
         ""
         ""
         “”
       “”
       “”
         “”
       “”
     “”
   “
Mode . . . . . . . . . : "
         “<select name=“ST”>”
             "<option value=“0”> STATIC "
             "<option value=“1”> DHCP "
         “”
       “
IP : <input type=“text” size=“1” maxlength=“3” name=“IP0” value=“192”> . <input type=“text” size=“1” maxlength=“3” name=“IP1” value=“168”> . <input type=“text” size=“1” maxlength=“3” name=“IP2” value=“0”> . <input type=“text” size=“1” maxlength=“3” name=“IP3” value=“10”>
MsR : <input type=“text” size=“1” maxlength=“3” name=“MSR0” value=“255”> . <input type=“text” size=“1” maxlength=“3” name=“MSR1” value=“255”> . <input type=“text” size=“1” maxlength=“3” name=“MSR2” value=“255”> . <input type=“text” size=“1” maxlength=“3” name=“MSR3” value=“0”>
Passerelle : <input type=“text” size=“1” maxlength=“3” name=“GAT0” value=“192”> . <input type=“text” size=“1” maxlength=“3” name=“GAT1” value=“168”> . <input type=“text” size=“1” maxlength=“3” name=“GAT2” value=“0”> . <input type=“text” size=“1” maxlength=“3” name=“GAT3” value=“254”>
<input type=“submit” value=” SUBMIT “>

   “

   "
"
 “”
“\n”   ;
// Fin de la page WEB

[/color]
```

Fonctionnement du code :

Les variables en EEPROM sont sous forme d’un struct

[sub][color=blue]
typedef struct {                                        // https://www.squalenet.net/fr/ti/tutorial_c/14-structures-unions.php5
  uint8_t validity;                                     // ID Cette variable permet de verifier que l'EEPROM a deja ete programme par ce programme Valeur de test pour savoir si des donnes sont valid en EEPROM (de type Adresse IP)
  boolean led;                                          // Etat de la LED
  
  uint8_t mac[6];                                       // local MAC address
  boolean dhcp;                                         // Arduino configure en IP STATC = 0 // Arduino configure en IP DHCP = 1 
  uint8_t ip[4];                                        // local IP address
  uint8_t subnet[4];  
  uint8_t gateway[4];
  uint16_t port;                                        // Port serveur WEB
} conf_t;
conf_t conf;  
[/color][/sub]

Le programme principal :

il est constitué de la partie classique du serveur WEB

Le tampon est dans un tableau pour éviter d’utiliser un string (qui pose des problèmes de gestion mémoire)

[sub][color=blue]
EthernetClient client = server.available();                                                   // écouter les clients entrants (Create a client connection)


 if (client) {                                                                                // client obtenu?
  Serial.println();  Serial.println(F("*****  Nouveau Client connecte :"));                   // Si un nouveau client se connecte,imprimer un message sur le port série

        boolean currentLineIsBlank = true;
        while (client.connected()) {
            if (client.available()) {                                                         // données client disponibles pour lire lire 1 octet (caractère) du clientla dernière ligne de la demande du client est vide et se termine par \ n 
                c = client.read();                                                            // répondre au client seulement après la dernière ligne reçue  Tamponner la première partie de la requête HTTP dans le tableau HTTP_req (chaîne)                                                                    
                                                                                              // laisse le dernier élément dans le tableau de 0 à null termine la chaîne (REQ_BUF_SZ - 1)
                if (req_index < (REQ_BUF_SZ - 1)) {
                    HTTP_req[req_index] = c;                                                  // enregistrer le caractère de requête HTTP
                    req_index++;
                }               
                //Serial.print(c);                                                            // Impression du caractère de requête HTTP sur le moniteur série                                                                      
                if (c == '\n' && currentLineIsBlank) {                                        // la dernière ligne de la requête client est vide et se termine par \ n
                                                                                              // répond au client seulement après la dernière ligne reçue// open requested web page file

                    for (int i = 0 ; i < req_index ; i++) {   Serial.print(HTTP_req[i]); }    // Impression du caractère de requête HTTP sur le moniteur série 
                                       
                    // Envoie Page WEB index.html ou confIP.html
                    PageWEB(client);

                    delay(10);

                    // réinitialiser l'index du tampon et tous les éléments du tampon à 0
                    req_index = 0;
                    StrClear(HTTP_req, REQ_BUF_SZ);
                    //Serial.println(F"Reset HTTP_req"));
                    break;
                } // end if (c == '\n' && currentLineIsBlank)
                
                
                if (c == '\n') {                                                                // chaque ligne de texte reçue du client se termine par \r \n
                                                                                                // dernier caractère en ligne du texte reçu Commencer une nouvelle ligne avec le prochain caractère lu
                  currentLineIsBlank = true;
                } 
                else if (c != '\r') {                                                           // un caractère de texte a été reçu du client
                  currentLineIsBlank = false;
                }
            } // end if (client.available())
        } // end while (client.connected())
        delay(10);                                                                              // donnez au navigateur le temps de recevoir les données
        client.stop();                                                                          // close the connection
        Serial.println(F("*****  Client Deconnecte (Fin boucle)"));
        SerialBarr(); // "-------------------------------------------------------
        Serial.println();
        Serial.println(F("> "));
    } // end if (client)
[/color][/sub]

Un sous-programme gère les deux pages WEB
C’est l’endroit ou l’on gère les postes la page WEB pour cela on utilise un sscanf qui décortique le POST par le navigateur WEB
(NB le POST c’est quand on clic sur SUBMIT)

```
[color=blue]

/*********************************************************************************************************************

  • Sous Programme
  • Fonction : Envoie une page WEB
  • utilise la sous prog envoiPageWEB (pour envoyer avec tempon le site en utilisant les variables PROGMEM)
    /*********************************************************************************************************************
  • Entree : (client de EtherntClient)
  • Sortie : Envoie des DATA vers le navigateur WEB
    /********************/
    void PageWEB(EthernetClient &client)
    {
    Serial.println(); Serial.println();
    // Page HTML : ConfPage.html (Page 2)

     if (StrContains(HTTP_req ,“GET /ConfPage.html”) ) {                                      
           Serial.println(F("Envoie Page WEB : ConfigIP.html "));
           ramFree(nullptr);
           
           // envoiPageWEB ( nom du client, Nom de la chaine PROGMEM, taille de la chaine PROGMEM );
           EnvoiPageWEB(client, page_WEB_ConfigIP , strlen_P(page_WEB_ConfigIP));

// Exemple d’envoie du navigateur sur la liaison serie
       /*  Envoie de la page WEB
           http://192.168.0.190/ConfPage.html?ST=0&IP0=192&IP1=168&IP2=0&IP3=15&MSR0=255&MSR1=255&MSR2=255&MSR3=0&GAT0=192&GAT1=168&GAT2=0&GAT3=254
       // Recu pr la liason serie
           GET /ConfPage.html?ST=0&IP0=192&IP1=168&IP2=0&IP3=15&MSR0=255&MSR1=255&MSR2=255&MSR3=0&GAT0=192&GAT1=168&GAT2=0&GAT3=254 HTTP/1.1
           Host: 192.168.0.14
           Connection: keep-alive
           Accept: text/html,ap
       */
       // Fonction sscanf permet de tranformer le tableau de char avec les infos de la conf ip et les mettre dans la structure conf.IP conf.gateway …                          
            // C library function - sscanf() - Tutorialspoint          // http://koor.fr/C/cstdio/fscanf.wp  //https://en.cppreference.com/w/c/io/fscanf
            // Fonction scanf qui decoupe la chaine de caractere en fonction de delimiteur      // c - Using scanf for reading an unsigned char - Stack Overflow
       //sscanf(HTTP_req, “GET /ConfPage.html?ST=%hhu&IP0=%hhu&IP1=%hhu&IP2=%hhu&IP3=%hhu&MSR0=%hhu&MSR1=%hhu&MSR2=%hhu&MSR3=%hhu&GAT0=%hhu&GAT1=%hhu&GAT2=%hhu&GAT3=%hhu”, &conf.dhcp, &conf.ip[0], &conf.ip[1], &conf.ip[2], &conf.ip[3], &conf.subnet[0], &conf.subnet[1], &conf.subnet[2], &conf.subnet[3], &conf.gateway[0], &conf.gateway[1], &conf.gateway[2], &conf.gateway[3] );

// A l’acces a la page WEB de configuration IP par le navigateur celle ci est execute une fois (et donc bug et REST !!!) on met donc un compteur pour ne pas executer le reset la permiere fois
       //Serial.print("Compteur : "); Serial.println(compteur);
       if (compteur == 1)
           {
               sscanf(HTTP_req, “GET /ConfPage.html?ST=%hhu&IP0=%hhu&IP1=%hhu&IP2=%hhu&IP3=%hhu&MSR0=%hhu&MSR1=%hhu&MSR2=%hhu&MSR3=%hhu&GAT0=%hhu&GAT1=%hhu&GAT2=%hhu&GAT3=%hhu”, &conf.dhcp, &conf.ip[0], &conf.ip[1], &conf.ip[2], &conf.ip[3], &conf.subnet[0], &conf.subnet[1], &conf.subnet[2], &conf.subnet[3], &conf.gateway[0], &conf.gateway[1], &conf.gateway[2], &conf.gateway[3] );

delay(1);  
               printConf(“1”);                    // Affiche toute la fonfiguration reseau
               saveConf(nullptr);
               
               Serial.println(F(“Enregistrement de la configuration IP en EEPROM : Ok!”)); Serial.println();
               client.print(F("

Configuration Enregistre

“));
               client.print(F(“L’Arduino est accessible dans quelques instant a l’adresse IP :  “));  
               if( conf.dhcp == 0)
                  {
                      client.print(conf.ip[0],DEC);   client.print(”.”);     client.print(conf.ip[1],DEC);     client.print(”.");     client.print(conf.ip[2],DEC);     client.print(".");     client.print(conf.ip[3],DEC);  
                  }
               else client.print(F(“Arduino DHCP OK !”));
                 
               client.print(F("

RESET Arduino : Now …

“));
               Serial.println(F(”------- RESET ARDUINO !!! -------")); Serial.println();
               compteur = 0;
               resetNow(nullptr);
           }

compteur++;
       return;                
 } // Fin Page WEB configuration

// Page HTML : index.html*************************************************************************************************
                               
if (StrContains(HTTP_req, "GET / ") || StrContains(HTTP_req, “GET /index.htm”) || StrContains(HTTP_req, “?BoutonON”) || StrContains(HTTP_req, “?BoutonOFF”) ) {
      Serial.println(F(“Envoie Page WEB : index.html”));
      ramFree(nullptr);
     
      // envoiPageWEB ( nom du cleint, Nom de la chaine PROGMEM, taille de la chaine PROGMEM
      EnvoiPageWEB(client, page_WEB_index_HTML , strlen_P(page_WEB_index_HTML));

delay(1);
     
      //client.stop();                                                //stopping client
      // Partie Controle des Boutons
      if (StrContains(HTTP_req ,"?BoutonON") ){
           conf.led = 1;
           digitalWrite(pinLED, HIGH);
           Serial.println(F(“LED est ON”));
           //client.print(F("p { color: green;}

LED est ON

"));            client.print(F("

LED est ON

"));       }       if (StrContains(HTTP_req ,"?BoutonOFF") ){            conf.led = 0;            digitalWrite(pinLED, LOW);            Serial.println(F("LED est OFF"));            client.print(F("

LED est OFF

"));       }       EEPROM.put(adressBase+1, conf.led);       //return;

} // Fin page HTML
// ***********************************************************************************************************************  
     // ICI on peut placer une autre pages WEB

}

[/color]
```

Et pour finir les pages WEB sont envoyées avec un sous-programme qui envoie la page WEB sous forme d’un PROGMEM

[sup][color=blue]
/*********************************************************************************************************************
* Sous Programme
* Fonction : Envoie une page WEB en utilisant les variables PROGMEM
/*********************************************************************************************************************
* Entree : Nom du PROGMEM
* 
* Sortie : Envoie des DATA vers le navigateur WEB
/*********************************************************************************************************************/
void EnvoiPageWEB(EthernetClient &client, const char *str_P, int taille)      
{
  const int tailleTampon = 32;                                                     // taille du tampon d'envoi
  char buffer[tailleTampon];
  int restant = taille;
  const char *aEnvoyer = str_P;                                                     // Charge le nom du PROGMEM reponse_debut ou reponse_fin
  int tailleEnvoi = tailleTampon;

  Serial.println(F("Routine EnvoiPageWEB : ")); 
  ramFree(nullptr);

  while  (restant > 0) {
         if  (tailleEnvoi > restant)
             tailleEnvoi = restant;
             
         memcpy_P(buffer, aEnvoyer, tailleEnvoi);                                    // void *   memcpy_P (void *, const void *, size_t)
         
         if  (client.write((const uint8_t *)buffer, tailleEnvoi) != tailleEnvoi)
             return;                                                                 // erreur
        
        restant -= tailleEnvoi;
        aEnvoyer += tailleEnvoi;
        }
}

[/color][/sup]

Voilà le projet et joint ici

Attention il consomme quasi toute la mémoire de l’Arduino :wink:

Il faudra enlever des commentaires … je les ai laissées pour des raisons de debbugage

Le code est conçu pour pouvoir enlever facilement des fonctions

La librairie du CmdLine se trouve également ici : https://github.com/IndustrialShields/arduino-CmdLine

Ce code s’est inspiré d’exemples déjà existants trouvés sur ce site et sur Internet

Si jamais vous réalisez des projets utilisant ce code, n’hésiter pas à les mettre en dessous :
Car tout ce qui est fait n’est plus à faire…

PriseIP_SrvWEB_Eth_WEBSerialConfIP_BoutonWEB_v22.ino (26.8 KB)

ConfigIP_Serial_CmdLine.h (16.1 KB)

ConfIP_HTML_PROGMEM.h (2.86 KB)

index_HTML_PROGMEM.h (2.61 KB)

Bonjour,
J’ai découvert une erreur dans la librairie cmdLine :
En effet quand on met une erreur dans la commande : cela peut faire rebouter l’arduino
J’ai donc cherché pourquoi (après plusieurs heure de recherche pour analyser la librairie et perdu quelques neurone au passage)

J’ai trouvé : l’erreur vient de l’exemple donné avec la librairie (mais pas la librairie elle-même)

Dans la déclaration de la librairie : cmdLine.begin(commands, sizeof(commands));
Le sizeof renvoie la taille du tableau or qu’il devrait renvoyer le nombre de commandes à tester

Il faut donc déclarer

#define countof(commands) (sizeof(commands)/sizeof(commands[0]))

Puis dans le setup

 cmdLine.begin(commands, countof(commands));

et là miracle plus de bug !!!!
Cette librairie et génial car elle permet d’envoyer des commandes (avec argument) en sérial commande et déclencher des sous-programmes de l’arduino

Couplé avec l’EEPROM on peut même les conserver
On peut ainsi entrer des paramètres de configuration (comme l’adresse ip, un nombre de récepteurs out tout ce qu’on veut)

Maintenant je voudrai permettre d’avoir le cmdLine en Telnet

Cela permettrai de commander l’arduino via une connexion Telnet en plus du sérial

J’ai presque fini, mais il reste un petit problème :
Il faut que j’envoie les messages sérial également sur le Telnet (logique)

On peut faire :

</sub> <sub>Serial.println(F("bon envoyer le même message deux fois … c’est nul non ?")); clientTelnet.println(F("bon envoyer le même message deux fois … c’est nul non ?"));</sub> <sub>

Donc j’ai détourné la fonction sérial pour l’envoyer deux fois :slight_smile:

</sub> <sub>void Println(char * arg) {   Serial.println(arg);   clientTelnet.println(arg); }</sub> <sub>

Ça marche pour

</sub> <sub>Println("yes ça marche !!!") ;</sub> <sub>

Mais quand je fais

Println(F("ça marche plus ")) ;

Et pour l’instant… un un F près ….

L’erreur :

</sub> <sub>error: cannot convert 'const __FlashStringHelper*' to 'char*' for argument '1' to 'void Println(char*)' cannot convert 'const __FlashStringHelper*' to 'char*' for argument '1' to 'void Println(char*)'</sub> <sub>

Si quelqu’un a une idée , je lui en serai reconnaissant