Pages Web multiples

Bonsoir,
Je suis à 2 doigts de terminer un projet "cossu" et il ne me reste plus qu'un seul point à régler.
Les exemples de serveurs Web sont pléthoriques sur le net, mais uniquement basés sur une seule page.
Mon projet IoT implique 7 pages (légères) articulées à partir d'une page racine.
Je viens enfin à trouver un exemple que j'ai aisément appliqué sur un ESP32 NodeMcu DevKit C V4.
Je l'ai trouvé sur un blog Arduino Slovaque ici
Ce site n'offre pas de possibilité de contact autre que le blog. Pas simple vu ma pratique du slovaque ...
Mon code est volumineux et pourvu de 7 sous dossiers dans l'IDE (1 par page). Je ne peux donc pas le poster ici, mais mon besoin porte sur une des 3 pages de l'exemple (n'importe laquelle) avec cette question simple :
Comment inclure des variables et lignes de sketch (genre if, else, etc...) dans ce type de page html ?
J'ai besoin d'afficher des valeurs de variables mais aussi de moduler l'affichage de boutons en fonction d'autres variables.

Bonjour, si le besoin est de modifier le contenu de la page en fonction de condition (ex si variable>valeur alors mettre le texte en rouge sinon en vert) le plus simple amha est de passer par du code javascript a inclure dans le html.
Cela dépend évidemment de ta connaissance du langage, mais pour afficher / masquer des éléments cela se fait assez facilement.
Un exemple plus precis permettrait de répondre plus précisément :wink:

Cela peut se faire de différentes façons :

JS, comme le suggère caps1g3f, il faudra passer des paramètres à la page pour que le code JS puisse prendre les décisions.

Le code C peut également afficher la page différemment suivant le contexte, mais pour cela il faudrait savoir quel serveur est utilisé (WiFiServer, WebServer, ESPAsyncWebServer, etc.), et comment sont stockées les pages (FLASH, SPIFFS, etc.).

ESPAsyncWebServer par exemple possède une notion de templates, qui pourrait être d'une grande utilité.

Merci à vous 2 pour ce début d'éclairage.
Pour vous permettre de mieux cerner mon besoin, voici le code principal gérant l'articulation des pages :

#include <WiFi.h>
#include <WebServer.h>

#include "page1.h"
#include "page2.h"
#include "page3.h"
#include "page4.h"
#include "page5.h"
#include "page6.h"
#include "page7.h"

#define WIFI_SSID "xxxxxxxxxxxxxxxxxxxxxxxxxxxx"
#define WIFI_PASSWORD "yyyyyyyyyyyyyyyyyyyyyyyyyyyyyyy"

WebServer server(80); 

void handleRoot() {
  Serial.println("GET /");
  server.send(200, "text/html", htmlPage1);
}

void handlePage2() {
  Serial.println("GET /page2");
  server.send(200, "text/html", htmlPage2);
}

void handlePage3() {
  Serial.println("GET /page3");
  server.send(200, "text/html", htmlPage3);
}

void handlePage4() {
  Serial.println("GET /page4");
  server.send(200, "text/html", htmlPage4);
}

void handlePage5() {
  Serial.println("GET /page5");
  server.send(200, "text/html", htmlPage5);
}

void handlePage6() {
  Serial.println("GET /page6");
  server.send(200, "text/html", htmlPage6);
}

void handlePage7() {
  Serial.println("GET /page7");
  server.send(200, "text/html", htmlPage7);
}
int tch = 64;
void setup(void){
  Serial.begin(115200);
  WiFi.begin(WIFI_SSID, WIFI_PASSWORD);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  server.on("/", handleRoot);
  server.on("/page2", handlePage2);
  server.on("/page3", handlePage3);
  server.on("/page4", handlePage4);
  server.on("/page5", handlePage5);
  server.on("/page6", handlePage6);
  server.on("/page7", handlePage7);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void){
  server.handleClient();
}type or paste code here

puis ensuite le code de la page racine (page1.h) qui affiche et gère le choix des 7 boutons. Vous remarquerez des boutons désactivés (grisés) comme bien sûr celui de la page1 (concernée) mais aussi 2 autres qui vont devoir devenir activés en fonction d'une variable élaborée par le processus (pas encore inclus dans le sketch principal, mais testé par ailleurs) :

const char htmlPage1[] PROGMEM = R"=====(
<html>

<head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <title>Chauffage - Accueil</title>
</head>

<body>
    <div class="container">
        <p><center>    
        <B>Chauffage</B>
            <br><br><B>
            <a class="btn btn-primary disabled" href="/" role="button">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Accueil&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a>
            <br><br>
            <a class="btn btn-primary" href="/page2" role="button">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Etat&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a>
            <br><br>
            <a class="btn btn-primary" href="/page3" role="button">&nbsp;&nbsp;Statistiques&nbsp;&nbsp;</a>
            <br><br>
            <a class="btn btn-primary disabled" href="/page4" role="button">&nbsp;&nbsp;Relance Allumage&nbsp;&nbsp;</a>
            <br><br>
            <a class="btn btn-primary disabled" href="/page5" role="button">Relance All + Pellets</a>
            <br><br>
            <a class="btn btn-primary" href="/page6" role="button">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Forçage Salon&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</a>
            <br><br>
            <a class="btn btn-primary" href="/page7" role="button">Changement d'heure</a>
            </B>
        </p>

        <p><small>Copyright (C) 2021 Jackjean</small></p>
    </div>
</body>

</html>
)=====";

Pour finir la page2 qui ne propose qu'un bouton de retour à la page initiale, mais va devoir afficher bon nombre de valeurs int, float et String. Les valeurs de l'exemple figurent dans la page pour exemple. Cette page sera rafraîchie toutes les 3 ou 5s (refresh).
J'ai décrit en sketch principal une variable int tch = 65 que je veux afficher à la place de la valeur 64 dans la ligne "Chaudière".

const char htmlPage2[] PROGMEM = R"=====(
<html>

<head>
    <meta http-equiv="content-type" content="text/html;charset=UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script>
    <title>Etat Locaux et Installation</title>
</head>

<body>
        <div class="container">
        <center>
        <B>Etat locaux et installation<br><br></B>
        </center>
        <p STYLE="padding:0 0 0 80px;">
    </p>
    <table>
      <tr>
        <td>Salon</td>
        <td><B>20,3°</td>
        <td><B>&#9679</td>
        <td><B>21,0°</td>
      </tr>
      <tr>
        <td>Ch. Parents&nbsp&nbsp&nbsp&nbsp</td>
        <td><B>18,1°&nbsp&nbsp</td>
        <td><B>&#9679&nbsp&nbsp</td>
        <td><B>15,0°&nbsp&nbsp</td>
      </tr>
      <tr>
        <td>Ch. Eva</td>
        <td><B>18,7°</td>
        <td><B>&#9679</td>
        <td><B>15,0°</td>
      </tr>
      <tr>
        <td>Ch. Victor</td>
        <td><B>16,3°</td>
        <td><B>&#9679</td>
        <td><B>15,0°</td>
      </tr>   
    </table>
    <br>
    <table>
      <tr>
        <td>Extérieur</td>
        <td><B>14°</td>            
      </tr>
      <tr>
        <td>Chaudière</td>
        <td><B>64°</td>            
      </tr>
      <tr>
        <td>Circuit</td>
        <td><B>42°</td>            
      </tr>
      <tr>
        <td>Eau chaude</td>
        <td><B>49°</td>            
      </tr>
      <tr>
        <td>Consommation&nbsp&nbsp&nbsp&nbsp</td>
        <td><B>2356s</td>            
      </tr>
    </table>
    <br>
    <table>
      <tr>
        <td>Etat&nbsp&nbsp&nbsp&nbsp</td>
        <td><B>Chaudière prête</td>            
      </tr>
    </table>
      <center>
      <br>
            <a class="btn btn-primary" href="/" role="button">Retour</a>
            <center>
        </p>
        <p><small>Copyright (C) 2021 Jackjean</small></</p>
    </div>
</body>

</html>
)=====";

J'ai inclus le caractère &#9679 (rond plein) dans chaque ligne de local symbolisant l'état 1 du besoin de chauffe, caractère que je veux passer en rond vide lorsque l'état passe à 0. Je verrai pour trouver un addon de led allumée / éteinte.

Les pages suivantes sont beaucoup plus simples et n'exigent rien de plus que ce j'ai expliqué pour les premières.

Je rencontre pas mal d'exemples qui utilisent apparemment une méthode différente puisque chaque ligne Html est envoyée par "page +".
J'en suis là.

Un excellent tuto sur ce même site devrait permettre d'y voir plus clair. Comme pour tout, il n'y a pas une mais des solutions. Celle présentée à l'avantage d'être hyper didactique et bien rédigée

Apparemment le choix de jackjean s'est porté sur la librairie WebServer.
Prenons un exemple simple : je veux pouvoir modifier le titre de la page.

    <title>Chauffage - Accueil</title>

Si le nombre de variables à afficher est faible :
On peut envoyer le début de la page jusqu'à <title>, envoyer le titre, et envoyer la fin de la page, à partir de </title>.
Cela revient à créer deux chaînes htmlPage1_1 et htmlPage1_2.
Si je veux envoyer plusieurs variables, il faudra augmenter le nombre de morceaux.

On peut également utiliser un formatage à l'aide de sprintf(). Dans la page il suffit de remplacer :

    <title>Chauffage - Accueil</title>

par :

    <title>%s</title>

Pour modifier le titre il suffira d'écrire :

char buffer[2000];
  sprintf(buffer, htmlPage1, "Chauffage");

Pour afficher des variables entières ou flottantes, on utilisera %d ou %f (voir la doc de sprintf()).
Le buffer doit avoir au moins la taille de la page + les différentes variables.
Si le contenu de la page change, il faudra veiller à ce que la taille du buffer évolue aussi. DANGER !

Si le nombre de variables à afficher est important :
Je conseillerais de jeter un œil ici :

Sincèrement, il y aurait tout à gagner à remplacer WebServer par ESPAsyncWebServer et ses templates. Cela ne fait pas une grosse différence au niveau code.

Également, les pages auraient intérêt à être stockées en SPIFFS plutôt que PROGMEM.

Dans cet autre exemple, l'auteur insère des boutons dans une page :

Merci pour vos contributions.
Je procède par ordre pour ne pas m'éparpiller.
@ hbachetti
Je suis dans le "tronçonnage" de ma page1, mais dans mon ignorance, je bute sur la manière de "coudre" ces pages partielles ensemble, à la fois dans le sketch principal, et dans l'écriture des en-têtes et fin de pages de nouveaux onglets créés...

entre-temps j'ai tenté le serveur asynchrone qui marche bien pour le contenu variable, mais comme presque toujours, je trouve des réponses ponctuelles, mais l'exemple ne dit pas comment intégrer cette page dans une suite de plusieurs autres.

Dans une prochaine étape, après avoir assimilé "la couture", je testerai les autres propositions (SPIFFS entre autres).

J'ai retrouvé un exemple plus simple, sans saucissonnage :

const char webRoot[] = R"(<!DOCTYPE HTML>
<html><head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>TEST DE MISE EN PAGE</title>
<style type="text/css">
body {background-color: #00979c}
</style></head>
<body>
Temp&eacute;rature = %TEMP%&deg;C<br>
<button onclick="location.href='/tup'" type='button'>  +  </button>
<button onclick="location.href='/tdown'" type='button'>  -  </button>
<br><br><br>
Pression = %PRESSURE%hPa<br>
<button onclick="location.href='/pup'" type='button'>  +  </button>
<button onclick="location.href='/pdown'" type='button'>  -  </button>
</body>
</html>
)";
int temp = 22;
int pressure = 1013;

void handleRoot(void)
{
  String value(temp);
  String message = webRoot;
  message.replace("%TEMP%", value);
  value = pressure;
  message.replace("%PRESSURE%", value);
  server.send(200, "text/html", message);
}

Grand merci pour cette solution qui devrait me permettre de modifier en +/- des valeurs de variables sur une page supplémentaire "Réglages" que j'avais abandonnée tant que le reste ne tournait pas.
Par contre, sur quelle base dois-je insérer cette solution ?
Je sais, la stupidité de mes questions n'a d'égale que la bonne volonté des contributeurs que vous êtes :slight_smile:

Dans mon code htmlPage1 s'appelle webRoot.
Ensuite insérer dans le HTML les variables en les nommant selon le besoin : %CHAUDIERE% pour une température chaudière, %CIRCUIT%, etc.
Il n'y a plus qu'à remplacer les variables par leur valeur :

  String message = htmlPage1;
  message.replace("%CHAUDIERE%", String(tempChaudiere));
  message.replace("%CIRCUIT%", String(tempCircuit));
  server.send(200, "text/html", message);

Pourquoi avoir choisi des balises %VAR% ? parce qu'elles ne font pas partie du standard HTML.

BRAVO ! ROYAL ! j'en rêvais depuis longtemps...
ça fonctionne bien.
L'astuce du message.replace est une perle.
Merci aussi de m'avoir appris les balises % encadrant une variable.
Une fois le code complété par le reste du processus, on statuera si besoin sur les moyens d'économie de RAM et de flash.
Deux petites questions subsidiaires :
J'aime bien la possibilité d'éclaircir un bouton désactivé, ce qui s'écrit avec le terme "disabled"

<a class="btn btn-primary disabled" href="/" role="button">Accueil</a>

En supprimant "disabled" on rend le bouton actif
Je voudrais que cette désactivation dépende de l'état d'une variable, mais l'astuce du message.replace avec "%ACTIF% remplacé par "active" ou "" ne marche pas.

Seconde question :
J'aimerais visualiser un état (chauffe ou pas d'un local) par un symbole (ou widget) led allumée, led éteinte. Ce serait plus joli qu'un vulgaire gros point noir comme actuellement.
Une idée ?

Si une balise %ACTIF% est remplacée par "" ou "disabled" ça doit marcher.

Une petite image JPEG ?
Pour ce faire, je conseillerais de jeter un œil (encore une fois) à ESPAsyncWebServer, et bien sûr SPIFFS, car insérer une balise img ne suffit pas.

<img src="/led.jpg" width="50" height="50"><br>

Il faut aussi un handler pour servir l'image :

  server.on("/led.jpg", HTTP_GET, [](AsyncWebServerRequest * request) {
    request->send(SPIFFS, "/led.jpg", "image/jpg");
  });

Ultra-simple avec ESPAsyncWebServer !

Si une balise %ACTIF% est remplacée par "" ou "disabled" ça doit marcher.

Effectivement, ça marche (mauvais test sur des String).

Une petite image JPEG ?

J'ai résolu le besoin avec un caractère spécial &#9679 dont je change la couleur.

J'en suis actuellement à la dernière étape : inclure le code du webserver dans le code existant.
J'ai des erreurs de compilation concernant cet ajout.


'class WiFiServer' has no member named 'send'   server.send(200, "text/html", message);
                       				                   ^
'class WiFiServer' has no member named 'on'   server.on("/", handleRoot);
         					                         ^
'class WiFiServer' has no member named 'handleClient'   server.handleClient();
          				                                       ^

Les bibliotèques présentes dans le code du projet avant l'intégration du code wifiserver sont les suivantes :

#include <WiFi.h> 
#include <SPI.h>
#include <EspProwl.h>                // envoi de notification push
#include <NTPClient.h>               // obtention de l'heure
#include <ESP_Mail_Client.h>     // envoi de mail
#include <SD.h>
#include "FS.h"

Plutôt qu'insérer le code Webserver dans le code principal, j'ai commencé à faire l'inverse : inclure une à une les bibliothèques je code principal et vérifier la compilation.
Pour l'instant tout se compile (juste an ajoutant les bibliothèques, pas le code qui les utilise).
Je poursuis en ajoutant un à un les modules du code principal jusqu'à trouver l'incompatibilité.

Et en #4 le code inclut WebServer. Il faudrait savoir ...

WiFiServer, c'est la charrue sans tracteur.

Et en #4 le code inclut WebServer. Il faudrait savoir ...
WiFiServer, c'est la charrue sans tracteur.

Je suis d'accord, mais je ne sais pas d'où vient cette class WiFiSever car je n'ai inclus aucune bibliothèque de ce type.
En poursuivant la démarche d'intégration inverse (partant du code "websever" j'ai inclus module par module le code principal), la compilation s'est achevée sans erreur, et le serveur web fonctionne.
Par contre, je remarque que ma gestion de carte SD ne fonctionne plus (carte introuvable), alors qu'elle fonctionne quand WebServer n'est pas inclus.
J'ai trouvé par hasard cet article qui évoque une incompatibilité d'un sketch exemple avec l'ESP32 .

En général, une fois le module SD connecté, je recommande l'essai du sketch d'exemple CardInfo fourni avec l'IDE Arduino. Mais bien que ce sketch fonctionne correctement avec les cartes Arduino conventionnelles, avec l'ESP8266 et avec les cartes à base de STM32, il est incompatible avec l'ESP 32.

Nous allons plutôt nous rabattre sur le sketch SD_Test , accessible par le menu Fichier - Exemples - SD(esp32) - SD_Test .

J'ai suivi cette recommandation, (donc mon code est celui de l'exemple SD_Test mais à priori il demeure un problème à ce niveau.

Je trouve sur ce site une remarque intéressante :

Evitez d’utiliser les fonctions spécifiques pour que le code soit multi-plateforme. ReadBytes(), ReadString(), cardType()… Si ce n’est pas possible, utilisez la directive #define pour adapter le code pour chaque cible.

c'est justement le cas chez moi avec cet exemple de fonction de lecture :


> void readFileTemp(fs::FS &fs, const char * path){
    Serial.printf("Lecture de: %s\n", path);
    File file = fs.open(path);
    if(!file)
        { Serial.println("Echec d'ouverture du fichier en lecture");
          return; }
    Serial.print("Lecture du fichier: ");
    while(file.available()){
        MsgSD += file.readStringUntil('\n');
        Serial.println(MsgSD);
        Temp = MsgSD; 
        MsgSD = "";}   
    file.close();
}

Par contre je ne comprends pas le conseil

Si ce n’est pas possible, utilisez la directive #define pour adapter le code pour chaque cible.

Quel est l'intérêt de la SD ?
L'ESP32 dispose d'un système de fichiers FATFS en FLASH (taille maxi 1.4Mo).

J'avais juste peur de la durée de vie des cellules flash, mais après réflexion, s'agissant de sauvegarder des data qui ne sont que rarement modifiées, l'écriture ne les fatigue pas...
Je vais donc me laisser tenter.

Je viens d'effectuer l'ajout du spiffs_fat.csv et modifié le fichier boards.txt. Par contre après ajout du fichier ESP32FS.jar là où il doit être, et relance de l'IDE je ne vois pas l'arborescence du Menu "Outils / ESP32 Sketch Data Upload".
J'ai bien une rafale de versions ESP32 dans le gestionnaire de cartes mais pas l'arborescence ESP32 Sketch Data Upload.
J'ai pourtant intégré les éléments permettant de programmer l'ESP32 depuis plusieurs semaines, et ça marche.
J'aurais raté quelque chose?
j'ai pourtant relu les 4 chapitres précédents de votre blog sans rien trouver

Chez moi il est bien ici : arduino-1.8.11/tools/ESP32FS/tool/esp32fs.jar

Chez moi il est aussi ici :
Arduino/tools/ESP32FS/tool/esp32fs.jar
Sous "Arduino" j'ai
/hardware
/libraries
/tools
J'imagine que Arduino peut être situé dans Documents ?