Modifier une valeur venant d'une page HTML

Bonjour à toutes et à tous,
Dans mes échanges entre un serveur ESP32 et une page HTML, j'en suis au problème suivant : à l'aide d'un bouton et d'une case d'entrée de valeur, je voudrais que cette valeur retourne au serveur dans son code. J'ai déjà des morceaux :

      Puissance max : <input type="number" id="pMaxMod" value="400" min="1"> <button onclick="ajusterPmax()">Ajuster</button>
...
      <script>

        let pAjustee = 400;  // Limite par défaut du nombre de points affichés

        function ajusterPmax() {
          const newPmax = document.getElementById('pMaxMod').value;
          if (newPmax > 0) {
            pAjustee = parseInt(newPmax);
            chart.options.scales.y0.max = pAjustee;
            chart.options.scales.y0.min = -pAjustee;
            chart.update();
            alert("Puissance ajustée : " + pAjustee);
           // quel code à placer ici pour que la valeur pAjustee aille vers mon serveur ?
          }
        }

Quand je clique sur mon bouton Ajuster, grâce à la fonction ajusterPmax(), je modifie bien le paramètre voulu dans mon chart. Je voudrais retrouver cette valeur dans mon code pour modifier des comportements dans celui-ci. Je suppose qu'il faut les bonnes instructions dans la fonction ajusterPmax(), que ça appelle une fonction de ce type dans le code de l'ESP32 :

  server.on("/modifVal", HTTP_GET, [](AsyncWebServerRequest *request){
    ...
  });

Que mettre dans ces fonctions pour que ça marche.

Cordialement.

Pierre.

Le plus simple est de faire comme ton code de rafraichissement et utiliser l'ajax.
c'est à dira quelque chose comme ça

fetch('/modifVal?val='+pAjustee);

Je n'ai pas testé en vrai :see_no_evil:

Merci "terwal", mais que faut-il que je mette dans :

server.on("/modifVal", HTTP_GET, [](AsyncWebServerRequest *request){
    ...
  });

pour récupérer la donnée ?

Cordialement.

Pierre.

Je suppose que tu utilise ça ?
l'objet request posséde deux fonctions(hasParam et getParam) qui permettent de vérifier que tu as un paramètre val(ou autre, c'est celui que j'ai mis en exemple) et de récupérer la valeur.
Je n'ai pas vérifier, mais normalement tu récupère la valeur sous forme de chaine.

Edit1: j'ai modifié le lien du github de ESPAsyncWebServer

Merci "terwal", mais le lien ne fonctionne pas.

Cordialement.

Pierre.

effectivement, je ne sais pas d'où je l'ai sortie :slight_smile:

voici un exemple (tapé ici donc pas 100% sûr qu'il fonctionne)

On a une page web qui affiche un champ de type nombre avec une étiquette (et un petit champ vide en dessous pour des messages de service)

la page se charge et déclenche (window.onload) la fonction JS chargerValeur() qui effectue une requête asynchrone vers le serveur /demande pour obtenir la valeur à mettre dans le champ. ça permet de démarrer la page web avec la bonne valeur affichée.

Ensuite quand on clique sur le bouton, on a associé la fonction mettreAJour() qui effectue aussi une requête asynchrone pour appeler /maj?v=xxx avec xxx qui est la valeur qui est affichée dans le champ texte encodée avec encodeURIComponent() afin qu'elle puisse être utilisée en toute sécurité dans une URL. On profite du petit champ texte (en gris et de taille plus petite) pour afficher un message qui dit qu'on est en train d'envoyer cette valeur et quand la transaction est terminée (on a reçu le OK du serveur qu'il avait reçu la requête) alors on efface ce message.

J'aime bien mettre mon code HTML and une chaîne dite " Raw string literal". ça permet de ne pas avoir à s'embêter avec des caractères d'échappement et de taper son HTML comme on veut.

Le petit souci c'est que l'IDE ne sait pas bien les gérer et que ça met le bazar dans l'indentation automatique ensuite donc je mets généralement mon code HTML dans un fichier séparé html.h que j'importe dans le sketch.

Voici à quoi ça ressemblerait :

html.h

#ifndef HTML_H
#define HTML_H

const char* pageRacine = R"rawliteral(
<!DOCTYPE html>
<html lang="fr">
  <head>
    <meta charset="UTF-8">
    <title>Démo</title>
    <script>
      // Fonction pour récupérer la valeur initiale
      function chargerValeur() {
        var requete = new XMLHttpRequest();
        requete.open('GET', '/demande', true);
        requete.onload = function() {
          if (requete.status == 200) {
            document.getElementById('valeur').value = requete.responseText;
          }
        };
        requete.send();
      }

      // Fonction pour mettre à jour la valeur
      function mettreAJour() {
        var nouvelleValeur = document.getElementById('valeur').value;
        var requete = new XMLHttpRequest();
        requete.open('GET', '/maj?v=' + encodeURIComponent(nouvelleValeur), true);
        document.getElementById('statut').innerHTML = 'envoi de : ' + nouvelleValeur;
        requete.onload = function() {
          if (requete.status == 200) {
            document.getElementById('statut').innerHTML = ''; // on efface
          } else {
            document.getElementById('statut').innerHTML = 'Erreur lors de la mise à jour. Statut : ' + requete.status;
          }
        };
        requete.send();
      }

      // Appeler la fonction chargerValeur au chargement de la page
      window.onload = chargerValeur;
    </script>
  </head>
  <body>
    <h1>Gestion des paramètres</h1>
    <label for='valeur'>Valeur :</label>
    <input type='number' id='valeur' value='?' />
    <button onclick='mettreAJour()'>Mise à jour</button>
    <p id='statut' style="font-size: 0.8em; color: #555;"></p>
  </body>
</html>
)rawliteral";

#endif

Bon une fois ceci préparé, du côté du serveur on doit faire 3 choses :

  • fournir la page racine si on reçoit /
  • fournir la valeur courante si on reçoit /demande
  • mettre à jour la valeur courante avec le paramètre xxx si on reçoit /maj?v=xxx

le code est donc assez simple avec ESPAsyncWebServer

#include <WiFi.h>
#include <ESPAsyncWebServer.h>
#include "html.h" 

const char* ssid = "xxx";          // A MODIFIER
const char* motDePasse = "xxx";    // A MODIFIER

AsyncWebServer serveur(80);

long valeur = 0;

void setup() {
  Serial.begin(115200);

  Serial.print("Connexion WiFi...");
  WiFi.begin(ssid, motDePasse);
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.write(".");
  }
  Serial.println("\tConnecté");

  // Page racine
  serveur.on("/", HTTP_GET, [](AsyncWebServerRequest *requete) {
    requete->send(200, "text/html", pageRacine);
  });

  // Envoi de la valeur
  serveur.on("/demande", HTTP_GET, [](AsyncWebServerRequest *requete) {
    char tmpBuf[12];
    snprintf(tmpBuf, sizeof tmpBuf, "%ld", valeur); 
    requete->send(200, "text/plain", tmpBuf);
  });

  // Réception d'une nouvelle valeur
  serveur.on("/maj", HTTP_GET, [](AsyncWebServerRequest *requete) {
    if (requete->hasParam("v")) {
      String nouvelleValeur = requete->getParam("v")->value();
      valeur = nouvelleValeur.toInt();
      Serial.print("Nouvelle valeur : "); Serial.println(valeur);
      Serial.flush();
    }
    requete->send(200, "text/plain", "OK");
  });

  serveur.begin();

  Serial.print("Ouvrez votre navigateur sur http://");
  Serial.println(WiFi.localIP());
}

void loop() {}

ça devrait fonctionner mais j'ai peut être fait des bugs... Donc à tester, l'idée c'est de comprendre le principe.

S'il y a des questions, n'hésitez pas.

Bon, j'ai fait un melting pot des vos informations et ça donne :

côté HTML, la fonction appelée lorsque j'appuie sur le bouton :

        function ajusterPmax() {
          const newPmax = document.getElementById('pMaxMod').value;
          if (newPmax > 0) {
            pAjustee = parseInt(newPmax);
            fetch('/modifVal?val='+pAjustee);
          }
        }

et du côté code :

  server.on("/modifVal", HTTP_GET, [](AsyncWebServerRequest *request){
    Serial.println("Requête vue");
    String inputValue;
    if (request->hasParam("val")) {
      inputValue = request->getParam("val")->value();
      Serial.print("Valeur reçue : ");
      Serial.println(inputValue);
    }
  });

Je reçois bien la requête, mais je la reçois en permanence toutes les 3 ou 4 secondes ! Faut-il faire un acquittement ?

10:13:32.989 -> Requête vue
10:13:32.989 -> Valeur reçue : 1000
10:13:36.123 -> Requête vue
10:13:36.123 -> Valeur reçue : 1000
10:13:39.641 -> Requête vue
10:13:39.641 -> Valeur reçue : 1000
10:13:43.306 -> Requête vue
10:13:43.306 -> Valeur reçue : 1000
10:13:46.622 -> Requête vue
10:13:46.622 -> Valeur reçue : 1000
10:13:50.124 -> Requête vue
10:13:50.124 -> Valeur reçue : 1000
10:13:53.635 -> Requête vue
10:13:53.635 -> Valeur reçue : 1000

Cordialement.

Pierre.

tu peux donner ton code?
si tu fait le fetch toutes les 3 ou 4 secondes, c'est que tu appels la fonction ajusterPmax à ce rythme.

vous devez avoir un refresh automatique qui tourne ? si vous nous donnez le HTML on pourra les voir

et concernant

newPmax est une chaîne de caractères, car getElementById().value retourne toujours une chaîne.

➜ Lorsque vous comparez directement newPmax > 0, JavaScript effectue une conversion implicite en nombre, ce qui peut entraîner des comportements inattendus si la valeur n'est pas un nombre valide. Il est donc préférable de convertir explicitement newPmax en nombre avant la comparaison.

function ajusterPmax() {
  const newPmax = document.getElementById('pMaxMod').value;
  const parsedPmax = parseInt(newPmax, 10);  // On force la base 10 pour la conversion
  if (!isNaN(parsedPmax) && parsedPmax > 0) {
    fetch('/modifVal?val=' + parsedPmax);
  } else {
    console.error('La valeur entrée n\'est pas valide');
  }
}

Si c'est le code de ce que je mets en œuvre, je l'ai donné au post #8. Sinon, vous voulez tout le code ? Je veux bien, mais c'est long.

Non, je ne clique qu'une fois sur le bouton.

En ajoutant la dernière ligne à cette fonction, il n'y a plus répétition :

  server.on("/modifVal", HTTP_GET, [](AsyncWebServerRequest *request){
    Serial.println("Requête vue");
    String inputValue;
    if (request->hasParam("val")) {
      inputValue = request->getParam("val")->value();
      Serial.print("Valeur reçue : ");
      Serial.println(inputValue);
    }
    request->send(200, "text/plain", "OK"); // ça bloque la répétition !
  });

@J-M-L : je vais rajouter les garde-fous que tu m'indiques.

Cordialement.

Pierre

Au moins le code HTML pour voir ce qui déclenche le rappel

Dans le code #8, il n'y a pas tout le code HTML, qui fait l'appel de la fonction.
Par contre le code Arduino, n'est pas nécessaire effectivement.

tu ne faisais pas de send?
Je ne comprends pas ce que tu veux dire.

Voilà tout le code. Il y a la partie HTML et la partie routage.

HTML :

String pageHTML = R"rawliteral(
    <!DOCTYPE html>
    <html>
    <head>
      <meta charset="UTF-8">
      <meta name="viewport" content="width=device-width, initial-scale=1.0">
      <title>ESP32 Routeur photovoltaïque</title>
      <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
    </head>
    <body onload="init();">
      <h1 style="text-align: center;">ESP32 Routeur photovoltaïque</h1>
      <div style="text-align: center;">
        <button id="maj-DT">MaJ Date & Heure</button>
        <button onclick="updateChart(1);" class="button button-data">Forme Courant &amp; Tension</button> 
        <button onclick="updateChart(2);" class="button button-data">Historique Puissance</button>
        <a href="download-data"><button class="button button-data">Télécharger les données</button></a>
      </div>
      <div id = "txt1">
        <p>Date et Heure : <span id="DetH">0.00</span></p>
        <p>Puissance active (W) : <span id="PA">0.00</span></p>
      </div>
      <div id = "txt2">
        <p>Puissance max : <input type="number" id="pMaxMod" value="1000" min="0"> <button onclick="ajusterPmax()">Ajuster</button></p>
      </div>
      <canvas id="myChart1" width="800" height="300" style="display: none"></canvas>
      <canvas id="myChart2" width="800" height="300" style="display: none"></canvas>

      <script>
        let txt1 = document.getElementById("txt1");
        let txt2 = document.getElementById("txt2");

        const interval = 2000;
        let intervalId = -1;
        let pAjustee = 1000;  // Limite par défaut du nombre de points affichés

        function init() {
          txt1.style.display = "none";
          txt2.style.display = "none";
        }

        function ajusterPmax() {
          const newPmax = document.getElementById('pMaxMod').value;
          const parsedPmax = parseInt(newPmax, 10);  // On force la base 10 pour la conversion

          if (!isNaN(parsedPmax) && parsedPmax > 0) {
            pAjustee = parsedPmax;
            chart2.options.scales.y0.max = pAjustee;
            chart2.update();
            fetch('/modifVal?val='+pAjustee);
          }
        }
      
        document.getElementById('maj-DT').addEventListener('click', function() {
          fetch('/maj-DT')
            .then(response => response.text())
            .then(data => {
              document.getElementById('DetH').textContent = data;
            });
        });

        const ctx1 = document.getElementById('myChart1').getContext('2d');
        const chart1 = new Chart(ctx1, {
          type: 'line',
          data: {
            labels: [],
            datasets: [{
              label: "Tension",
              yAxisID: 'y0',
              data: [],
              borderColor: 'rgb(75, 192, 192)',
              tension: 0.3 // Filtrage du signal ; doit être compris entre 0 (pas de filtrage) et 1 (filtrage max)
            },
            {
              label: "Courant",
              yAxisID: 'y1',
              data: [],
              borderColor: 'rgb(192, 192, 75)',
              tension: 0.3
            }]
          },
          options: {
            scales: {
              x: {
                display: true,
                title : {
                  display: true,
                  text: 'Echantillons',
                }
              },
              y0: {
                display: true,
                position: 'left',
                title : {
                  display: true,
                  text: 'Tension (V)',
                }
              },
              y1: {
                display: true,
                position: 'left',
                title : {
                  display: true,
                  text: 'Courant (A)',
                }
              }
            }
          }
        });

        const ctx2 = document.getElementById('myChart2').getContext('2d');
        const chart2 = new Chart(ctx2, {
          type: 'line',
          data: {
            labels: [],
            datasets: [{
              pointRadius: 0,
              label: "Historique Puissance",
              yAxisID: 'y0',
              data: [],
              borderColor: 'blue',
              tension: 0.3 // Filtrage du signal ; doit être compris entre 0 (pas de filtrage) et 1 (filtrage max)
            },
            {
              pointRadius: 0,
              label: "Historique Tension",
              yAxisID: 'y1',
              data: [],
              borderColor: 'green',
              tension: 0.3
            },
            {
              pointRadius: 0,
              label: "Historique Courant",
              yAxisID: 'y2',
              data: [],
              borderColor: 'red',
              tension: 0.3
            }]
          },
          options: {
            scales: {
              x: {
                display: true,
                title : {
                  display: true,
                  text: 'Date et heure',
                }
              },
              y0: {
                min : 0,
                max : pAjustee,
                display: true,
                position: 'right',
                title : {
                  display: true,
                  text: 'Puissance (W)',
                }
              },
              y1: {
                min : 0,
                max : 300,
                display: true,
                position: 'left',
                title : {
                  display: true,
                  text: 'Tension (V)',
                }
              },
              y2: {
                min : 0,
                max : 20,
                display: true,
                position: 'left',
                title : {
                  display: true,
                  text: 'Courant (A)',
                }
              }
            }
          }
        });

        function updateChart(type) {
          if (type == 1) {

            fetch('/maj-DT')
              .then(response => response.text())
              .then(data => {
                document.getElementById('DetH').textContent = data;
              });

            fetch('/pActive')
              .then(response => response.text())
              .then(data => {
                document.getElementById('PA').textContent = data;
              });

            if (intervalId == -1) {
              intervalId = setInterval(updateChart, interval, 1);
            }
            fetch('/formeUI')
            .then(response => response.text())
            .then(data => {
              const rows = data.split('\n');
              const labels = [];
              const values0 = [];
              const values1 = [];
              let indx = 0;
              rows.forEach(row => {
                const [value0, value1] = row.split(',');
                labels.push(indx++);
                values0.push(parseFloat(value0));
                values1.push(parseFloat(value1));
              });             
              chart1.data.labels = labels;
              chart1.data.datasets[0].data = values0;
              chart1.data.datasets[1].data = values1;
              chart1.update();
            });
            txt1.style.display = 'block';
            txt2.style.display = 'none';
            document.getElementById('myChart1').style.display = 'block';
            document.getElementById('myChart2').style.display = 'none';
          } else {
            if (intervalId != -1) {
              clearInterval(intervalId);
            }
            intervalId = -1;
            fetch('/histoUIP')
            .then(response => response.text())
            .then(data => {
              const rows = data.split('\n');
              const labels = [];
              const values0 = [];
              const values1 = [];
              const values2 = [];
              rows.forEach(row => {
                const [label, value0, value1, value2] = row.split(',');
              labels.push(label);
                values0.push(parseFloat(value0));
                values1.push(parseFloat(value1));
                values2.push(parseFloat(value2));
              });             
              chart2.data.labels = labels;
              chart2.data.datasets[0].data = values0;
              chart2.data.datasets[1].data = values1;
              chart2.data.datasets[2].data = values2;
              chart2.update();
            });
            txt1.style.display = 'none';
            txt2.style.display = 'block';
            document.getElementById('myChart1').style.display = 'none';
            document.getElementById('myChart2').style.display = 'block';
          }
        }

      </script>
    </body>
    </html>
  )rawliteral";

le routage :

#ifndef ROUTES_H
#define ROUTES_H

#include <ESPAsyncWebServer.h>


String infUI; // Chaîne de caractère représentant la forme de la tension et du courant
float pAonde; // Valeur de la puissance active sur une période de 50 Hz


void setupRoutes(AsyncWebServer &server) {
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){ // Affichage de la page HTML
    
    request->send(200, "text/html", pageHTML);
  });

  server.on("/formeUI", HTTP_GET, [](AsyncWebServerRequest *request){ // Envoi du fichier de forme de la tension et du courant
    request->send(200, "text/plain", infUI); 
  });

  server.on("/pActive", HTTP_GET, [](AsyncWebServerRequest *request){ // Envoi de la puissance active 
    request->send(200, "text/plain", String(pAonde));
  });

  server.on("/histoUIP", HTTP_GET, [](AsyncWebServerRequest *request){ // Envoi du fichier d'historique pour affichage
    request->send(LittleFS, "/data.txt", String(), false);
  });

  server.on("/modifVal", HTTP_GET, [](AsyncWebServerRequest *request){
    Serial.println("Requête vue");
    String inputValue;
    if (request->hasParam("val")) {
      inputValue = request->getParam("val")->value();
      Serial.print("Valeur reçue : ");
      Serial.println(inputValue);
    }
    request->send(200, "text/plain", "OK"); // Cette ligne n'existe pas lorsqu'il y a répétition
  });

  server.on("/download-data", HTTP_GET, [](AsyncWebServerRequest *request){ // Envoi du fichier d'hisorique pour téléchargement
    request->send(LittleFS, "/data.txt", String(), true);
  });
  
}

#endif

Cordialement.

Pierre.

merci, du coup ça marche ou pas ?

En rajoutant la ligne request->send(200, "text/plain", "OK"); à la fin de server.on("/modifVal", HTTP_GET, [](AsyncWebServerRequest *request){, tout va bien.

Je suppose que c'est cette ligne qui manquait.

Cordialement.

Pierre.

c'est la ligne qui permet au client de terminer sa requête mais si je me souviens bien fecth() ne répète pas automatiquement une requête qui n'aboutit pas donc c'est bizarre que vous ayez eu une répétition

Je partage le commentaire de @J-M-L , c'est pour ça que je te demandais confirmation sur ce qui résolvait ton problème.

Si ca marche c'est le principal :slight_smile:
Je vais adapter mon script python, pour fonctionner avec ton HTML, si tu as d'autre question à l'avenir :slight_smile:

@terwal et @J-M-L , merci de votre soutient.

Et oui, j'aurai d'autres questions.

Cordialement.

Pierre.