WebServer con ESP8266: come posso leggere più variabili in un unico momento?

Ciao a tutti.
Da internet ho copiato il seguente codice:

#include <Arduino.h>
#ifdef ESP32
  #include <WiFi.h>
  #include <AsyncTCP.h>
#else
  #include <ESP8266WiFi.h>
  #include <ESPAsyncTCP.h>
#endif
#include <ESPAsyncWebServer.h>

AsyncWebServer server(80);

// REPLACE WITH YOUR NETWORK CREDENTIALS
const char* ssid = "NomeRete";
const char* password = "PswRete";

const char* PARAM_INPUT_1 = "input1";
const char* PARAM_INPUT_2 = "input2";
const char* PARAM_INPUT_3 = "input3";

// HTML web page to handle 3 input fields (input1, input2, input3)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
  <title>ESP Input Form</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  </head><body>
  <form action="/get">
    input1: <input type="text" name="input1">
    <input type="submit" value="Submit">
  </form><br>
  <form action="/get">
    input2: <input type="text" name="input2">
    <input type="submit" value="Submit">
  </form><br>
  <form action="/get">
    input3: <input type="text" name="input3">
    <input type="submit" value="Submit">
  </form>
</body></html>)rawliteral";

void notFound(AsyncWebServerRequest *request) {
  request->send(404, "text/plain", "Not found");
}

void setup() {
  Serial.begin(9600);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  if (WiFi.waitForConnectResult() != WL_CONNECTED) {
    Serial.println("WiFi Failed!");
    return;
  }

  Serial.println();
  Serial.print("IP Address: ");
  Serial.println(WiFi.localIP());

  // Send web page with input fields to client
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html);
  });

  // Send a GET request to <ESP_IP>/get?input1=<inputMessage>
  server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    String inputParam;
    // GET input1 value on <ESP_IP>/get?input1=<inputMessage>
    if (request->hasParam(PARAM_INPUT_1)) {
      inputMessage = request->getParam(PARAM_INPUT_1)->value();
      inputParam = PARAM_INPUT_1;
    
    // GET input2 value on <ESP_IP>/get?input2=<inputMessage>

      inputMessage = request->getParam(PARAM_INPUT_2)->value();
      inputParam = PARAM_INPUT_2;
    
    // GET input3 value on <ESP_IP>/get?input3=<inputMessage>

      inputMessage = request->getParam(PARAM_INPUT_3)->value();
      inputParam = PARAM_INPUT_3;
    }
    else {
      inputMessage = "No message sent";
      inputParam = "none";
    }
    Serial.println(inputMessage);
    request->send(200, "text/html", "HTTP GET request sent to your ESP on input field (" 
                                    + inputParam + ") with value: " + inputMessage +
                                     "<br><a href=\"/\">Return to Home Page</a>");
  });
  server.onNotFound(notFound);
  server.begin();
}

void loop() {
  
}

Con questo codice sulla pagina web escono tre caselle di testo in cui poter inserire dei dati, ed ogni casella ha il suo pulsante per l'invio del dato all'ESP8266.
Il codice funziona correttamente, ma io vorrei arrivare al punto in cui si ha un solo pulsante di invio dei dati per tutti e tre i valori. Questa cosa è possibile con questa libreria? Qualcuno può darmi una mano nella modifica del codice?
Grazie

Poi, sempre se possibile, vorrei eliminare il reindirizzamento ad un'altra pagina quando viene premuto il pulsante di invio del dato: attualmente, dopo ogni pressione del pulsante di invio, si ha un reindirizzamento ad una pagina in cui viene comunicato cosa è stato inviato, e poi viene mostrato un testo cliccabile per ritornare alla pagina iniziale. Io vorrei eliminare questo reindirizzamento se possibile

Grazie a tutti in anticipo

Tutto quello di cui hai bisogno, è necessario farlo lato HTML

La cosa più semplice è fare in modo che venga usato un solo pulsante per l'invio: nel codice HTML vengono creati 3 form distinti ed è sufficiente mettere tutti e tre gli input box all'interno di unico form.

  <form action="/get">
    input1: <input type="text" name="input1">
    <input type="submit" value="Submit">
    input2: <input type="text" name="input2">
    <input type="submit" value="Submit">
    input3: <input type="text" name="input3">
    <input type="submit" value="Submit">
  </form>

La seconda richiesta invece è un po' più complessa perché questo purtroppo è il comportamento di default del componente html form.

Evitarlo è leggermente laborioso perché richiede l'uso di un po' di JavaScript per intercettare il meccanismo e modificarne il comportamento.

Dopo pranzo se ho un minuto provo a riscrivere il codice html includendo questa modifica, intanto se vuoi puoi provare a dare uno sguardo all'HTML incluso nell'esempio handleFormData.ino di questa mia libreria (magari può tornarti anche utile per il tuo progetto) dove i sorgenti del webserver sono salvati nella memoria flash dell'esp, ma di fatto il principio è lo stesso.

1 Like

Ti ringrazio infinitamente per l'aiuto e la disponibilità!
Ora guardo quel che mi hai scritto e lo provo subito, poi mi studio il link che mi hai inviato

Grazie ancora!!

Allora... per gestire in modo più scalabile i dati che arrivano dalle forms senza avere il fastidioso reload della pagina, conviene modificare alcune proprietà del form stesso ovvero fare in modo che i dati inviati alla pagina target (impostata con la proprietà action) siano trasmessi usando il metodo HTTP POST e non con il GET come fa di default.

La parte HTML quindi diventa cosi.
Ho aggiunto un po' di stile e usato le label al posto del testo "grezzo" :crazy_face:
Ho inoltre aggiunto anche la proprietà custom data-result alla form in modo da avere un riferimento dove poter inserire un messaggio di feedback inviato dal server dopo il submit dei dati.

Lo script JavaScript che ho aggiunto in fondo infine è preso paro paro dall'esempio di cui ti ho messo il link nel post precedente ed è generico: se aggiungi una seconda form nella tua pagina, verrà gestita dinamicamente senza dover modificare alcunché (proprio come nell'esempio in questione).

// HTML web page to handle 3 input fields (input1, input2, input3)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
  <title>ESP Input Form</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <style> 
    input[type=text] {
      width: 20%;
      padding: 5px 10px;
      margin: 5px 0;
      box-sizing: border-box;
    }
  </style>
  </head><body>
  <form action="/get" method="post" data-result="form-result">
    <label for="input1">Input1: </label><input type="text" name="input1"><br>
    <label for="input2">Input2: </label><input type="text" name="input2"><br>
    <label for="input3">Input3: </label><input type="text" name="input3"><br><br>    
    <input type="submit" value="Submit">
  </form>
  <br><br>
  <p id="form-result"></p>
      
  <script>
    // This listener will prevent each form to reload page after submitting data
    document.addEventListener("submit", (e) => {
      const form = e.target;        // Store reference to form to make later code easier to read
      fetch(form.action, {          // Send form data to server using the Fetch API
         method: form.method,       // Will be POST method
         body: new FormData(form),         
      })     
      .then(response => response.text())  // Parse the server response
      .then(text => {                     // Do something with parsed response
        console.log(text);
        // Update the element with server reply (in this example <p id="form-result"></p>)
        const resEl = document.getElementById(form.dataset.result).innerHTML = text;
      });
      
      e.preventDefault();                 // Prevent the default form submit wich reload page
    });
  </script>
</body></html>)rawliteral";

A questo punto a livello di codice "Arduino" è necessario modificare anche la funzione lambda server.on("/get", HTTP_GET, [] (AsyncWebServerRequest *request) {..........} associata alla richiesta /get in modo da gestire il POST invece che il GET che quindi diventa:

// Con le forms è più comodo usare il metodo HTTP POST al posto di GET (a livello di JavaScript)
  server.on("/get", HTTP_POST, [] (AsyncWebServerRequest *request) {
    String inputMessage;
    String result = "No data sent";
      
    // Retrieve input1 value passed with POST method
    if (request->hasParam(PARAM_INPUT_1, true)) {
      inputMessage = request->getParam(PARAM_INPUT_1, true)->value();
      result = PARAM_INPUT_1;
      result += ": ";
      result += inputMessage;
      result += "; ";
    }
      
    // Retrieve input2 value passed with POST method
    if (request->hasParam(PARAM_INPUT_2, true)) {
      inputMessage = request->getParam(PARAM_INPUT_2, true)->value();
      result += PARAM_INPUT_2;
      result += ": ";
      result += inputMessage;
      result += "; ";
    }
    
    // Retrieve input3 value passed with POST method
    if (request->hasParam(PARAM_INPUT_3, true)) {
      inputMessage = request->getParam(PARAM_INPUT_3, true)->value();
      result += PARAM_INPUT_3;
      result += ": ";
      result += inputMessage;
      result += ";";
    }

    Serial.println(result); 
    request->send(200, "text/html", result);
  });
1 Like

No va beh... Assurdo!
Grazie infinite! Ma come hai fatto?
La parte HTML purtroppo non la capisco minimamente, non la conosco..
Per quanto riguarda invece la parte di Arduino, in pratica hai unito il risultato in una stringa?

La stringa concatenata è solo per rispondere qualcosa al client e fornire feedback sulla buona riuscita dell'operazione, puoi anche mandare un semplice "OK" ad esempio e semplificare tutto.

Immagino che tu questi valori che stai passando con la form li vuoi poi assegnare a delle variabili numeriche?

In questo caso tieni conto che la funzione
request->getParam(PARAM_INPUT_2, true).value() ti ritorna una String che devi quindi convertire in numero ad esempio con toInt()

Capito, grazie!
Per la conversione l'avevo letto, quello era l'ultimo dei problemi in pratica.. era tutto il resto il casino per me, non ci capisco nulla..
Grazie ancora!

Conosci un editor per HTML che permetta di visualizzare in tempo reale il risultato del codice? Come se fosse già sul web, così da rendersi subito conto di quel che si scrive
Ho provato Atom con il pacchetto "html-preview" ma non funziona, ho provato Visual Studio Code e funziona ma a pezzi, non aggiorna quel che scrivo ..

Altra domanda... Se io avessi un numero di variabili da leggere molto alto, intorno alle 300 variabili, dovrei fare 300 if come per le attuali tre variabili?

E terza domanda: nel caso io volessi spostare "const char index_html[] in un'altra scheda del codice per avere la situazione piú ordinata, come dovrei fare? perché spostandolo cosí subito, con un taglia/incolla, non essendo piú nella posizione originale non viene trovato poi il char nel comando presente nel void setup

Con la mia libreria esp-fs-webserver c'è un editor integrato che ti consente di modificare il sorgente HTML, JavaScript e CSS direttamente nella memoria flash dell'ESP8266.

In alternativa potresti installare un web server locale sul tuo PC, io ad esempio mi trovo molto bene con Laragon nella versione portable.

Oppure ancora puoi usare Visual Studio Code ed aggiungere il plugin Live Preview

Per quanto riguarda il discorso delle variabili, se hai bisogno di impostare via web circa 300 variabili, la prima cosa che mi viene da pensare è che il software che stai implementando è tutto tranne che user friendly e probabilmente dovresti rivedere qualcosa...
Potresti pensare di mandare un file JSON che contiene le tue variabili, ma rimane comunque un lavoro immenso esplicitare ogni singola cosa... sicuro che non ci sia un modo più "smart"?

Se sposti il const char index_html[] in un'altra scheda devi essere sicuro di includerla nel tuo sketch principale prima di usarlo:

index_html.h:

// HTML web page to handle 3 input fields (input1, input2, input3)
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
  <title>ESP Input Form</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
etc etc

sketch.ino:
#include "index_html.h"

1 Like

Grazie per i consigli sui software, forse con Visual Studio Code ed il plugin riesco a fare qualcosa per ora

Sinceramente non lo so, neanche so cos'é un file JSON o come farlo/impostarlo.. Cosí com'é il codice, servirebbero per forza 300 if giusto?

Per le schede hai ragione, mi era sfuggito il fatto di doverla includere, grazie!

Non necessariamente. L'if serve solo per assicurarsi che il parametro di cui vuoi recuperare il valore sia presente nei dati inviati dal browser, ma puoi anche dare per scontato che ci sia e saltare la verifica.

Rimane il fatto che devi comunque richiamare 300 volte l'istruzione
inputMessage = request->getParam(PARAM_INPUT_2, true)->value();, cosa da matti per come la vedo io...

Se i dati sono omogenei, ad esempio tutti int potresti fare un array [] e con un ciclo for andarli a leggere in un colpo solo.


int myIntVars[300];

for(int i=0; i<300; i++){
  String param = "input";
  param += i;
  myIntVars[i] = request->getParam(param, true)->value().toInt();
}

Si, io ho diviso i dati in array, scrivere 300 variabili era da suicidio... Un array penso sia la cosa migliore quando si hanno gruppi di variabili

Grazie infinite per gli aiuti che mi stai fornendo!
Quindi io elimino tutti gli if e metto la scrittura che mi hai detto tu, dico bene?
Poi magari posto il codice

Ci ho pensato ora peró... Io non ho tutte tutte le variabili sottoforma di int, ma ho int e float divise in array..

Altra domanda: se io inserisco due bottoni di invio dati, come posso distinguerli?

Mi sa che conviene fare una visita qui...

Ciao, Ale.

Ciao, sto giá seguendo quel sito per vedere le istruzioni HTML, ma il problema non é aggiungere un pulsante ma capire come riconoscerlo sull'ESP8266

Altra cosa che non so come fare, é inviare dei dati dall'ESP8266 al sito

Comunque volevo dire a cotestatnt che son riuscito a leggere tutti i valori grazie al tuo metodo con l'array

Provo a rispondere un po' per volta ai tanti quesiti...

Duplichi quanto fatto con i valori di tipo int per quelli di tipo float usando ovviamente toFloat() per la conversione

Detta cosi questa cosa non significa nulla, che devono fare questi pulsanti? Posso presumere che debbano inviare dei comandi distinti all'ESP8266. La cosa più immediata secondo me è fare in modo che i due pulsanti facciano una chiamata AJAX (Asynchronous JavaScript and XML) al server esp ovvero una richiesta asincrona più o meno come se stessi richiedendo una nuova pagina, ma senza ricaricare tutto.

In rete troverai moltissimi esempi su come fare ed uno lo trovi anche nel link che ti ho messo più su dove viene usato l'oggetto JavaScript XMLHttpRequest.

Io preferisco usare la più moderna e flessibile API fetch proprio come nel codice HTML che ti ho modificato più su, ma il risultato è del tutto equivalente:
la tua pagina web, DOPO aver caricato la pagina HTML, può inviare una seconda, terza, quarta o quante vuoi richieste asincrone al server per ricevere ulteriori dati che poi andrai a manipolare di conseguenza.

Questo è un errore in cui prima o poi incappano tutti quando si è agli inizi... nel protocollo HTTP, ovvero quello usato tra browser e server, la comunicazione viene SEMPRE avviata dal client (in questo caso il browser) ed è monodirezionale: il client fa la richiesta, il server risponde, fine delle comunicazioni!

Esistono "tecnologie web" che consentono di instaurare comunicazioni bidirezionali browser-server, ma direi che stai mettendo troppa carne al fuoco per il momento (giusto per completezza, una delle più utilizzate è il WebSocket).

Posso immaginare che un possibile scopo possa essere quello di fare in modo che la pagina web mostri i valori attuali delle variabili dell'ESP, giusto?
In tal caso, quello che potresti fare è usare la tecnica AJAX di cui ti ho accennato prima per inviare al server una richiesta di "aggiornamento dati" e quando il server risponde, vai ad aggiornare i valori degli elementi HTML di conseguenza.

Piú o meno, esatto, piú che altro proprio con un pulsante aggiuntivo sulla pagina web che richiede all'ESP di far vedere i dati salvati in essa
Troppo complicato?

Sisi ho fatto, alla fine ci son riuscito

#include <ESP8266WiFi.h>
#include <ESPAsyncTCP.h>
#include <ESPAsyncWebServer.h>

// Replace with your network credentials
const char* ssid = "TIMDIsola";
const char* password = "AlessandroDisola310103";

// Create AsyncWebServer object on port 80
AsyncWebServer server(80);

// Create an Event Source on /events
AsyncEventSource events("/events");

// Timer variables
unsigned long lastTime = 0;  
unsigned long timerDelay = 30000;

float temperature = 25.0;
float humidity = 88.0;
float pressure = 1080.0;

// Initialize WiFi
void initWiFi() {
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    Serial.print("Connecting to WiFi ..");
    while (WiFi.status() != WL_CONNECTED) {
        Serial.print('.');
        delay(1000);
    }
    Serial.println(WiFi.localIP());
}

String processor(const String& var){
  //Serial.println(var);
  if(var == "TEMPERATURE"){
    return String(temperature);
  }
  else if(var == "HUMIDITY"){
    return String(humidity);
  }
  else if(var == "PRESSURE"){
    return String(pressure);
  }
  return String();
}

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html><head>
    <title>Test dati</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <style> 
      input[type=text] {
        width: 8%;
        padding: 5px 10px;
        margin: 5px 0;
        box-sizing: border-box;
      }
    </style>
    </head>
    <body bgcolor = "#b3e6ff">
    <h1 style="color: #003399; text-align: center;">Invio dati web</h1>
    <br><br>
       
<form action="/get" method="post" data-result="form-result">

        <p>TEMPERATURE</p><p><p class="reading"><p id="temp">%TEMPERATURE%</p></p></p>


<script>
if (!!window.EventSource) {
 var source = new EventSource('/events');
 
 source.addEventListener('open', function(e) {
  console.log("Events Connected");
 }, false);
 source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
 }, false);
 
 source.addEventListener('message', function(e) {
  console.log("message", e.data);
 }, false);
 
 source.addEventListener('temperature', function(e) {
  console.log("temperature", e.data);
  document.getElementById("temp").innerHTML = e.data;
 }, false);
 
 source.addEventListener('humidity', function(e) {
  console.log("humidity", e.data);
  document.getElementById("hum").innerHTML = e.data;
 }, false);
 
 source.addEventListener('pressure', function(e) {
  console.log("pressure", e.data);
  document.getElementById("pres").innerHTML = e.data;
 }, false);
}
</script>


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

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

  // Handle Web Server
  server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
    request->send_P(200, "text/html", index_html, processor);
  });

  // Handle Web Server Events
  events.onConnect([](AsyncEventSourceClient *client){
    if(client->lastId()){
      Serial.printf("Client reconnected! Last message ID that it got is: %u\n", client->lastId());
    }
    // send event with message "hello!", id current millis
    // and set reconnect delay to 1 second
    client->send("hello!", NULL, millis(), 10000);
  });
  server.addHandler(&events);
  server.begin();
}

void loop() {

  if ((millis() - lastTime) > timerDelay) {
    Serial.printf("Temperature = %.2f ºC \n", temperature);
    Serial.printf("Humidity = %.2f \n", humidity);
    Serial.printf("Pressure = %.2f hPa \n", pressure);
    Serial.println();

    // Send Events to the Web Server with the Sensor Readings
    events.send("ping",NULL,millis());
    events.send(String(temperature).c_str(),"temperature",millis());
    events.send(String(humidity).c_str(),"humidity",millis());
    events.send(String(pressure).c_str(),"pressure",millis());
    
    lastTime = millis();
  }


}

Ho preso questo esempio dal web e l'ho modificato un po', togliendo la parte di lettura dei sensori ed impostando dei numeri fissi sulle variabili
Poi, ho modificato l'HTML nel modo che si vede, e qui nascono i problemi: se mantengo l'HTML originale, funziona tutto.
Se vado a modificare l'HTML come quello qui sopra, sul sito web ottengo una pagina bianca.
Se vado a rimuovere la riga <p>TEMPERATURE</p><p><p class="reading"><p id="temp">%TEMPERATURE%</p></p></p> invece il sito web viene visualizzato correttamente ma, ovviamente, nessun numero viene stampato visto che manca quel pezzetto. (Ho provato a lasciarne solo uno, ma in origine son tre i numeri inviati.
Dove sbaglio?

Questo l'HTML originale:

const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
  <title>ESP Web Server</title>
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
  <link rel="icon" href="data:,">
  <style>
    html {font-family: Arial; display: inline-block; text-align: center;}
    p { font-size: 1.2rem;}
    body {  margin: 0;}
    .topnav { overflow: hidden; background-color: #50B8B4; color: white; font-size: 1rem; }
    .content { padding: 20px; }
    .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
    .cards { max-width: 800px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); }
    .reading { font-size: 1.4rem; }
  </style>
</head>
<body>
  <div class="topnav">
    <h1>BME280 WEB SERVER (SSE)</h1>
  </div>
  <div class="content">
    <div class="cards">
      <div class="card">
        <p><i class="fas fa-thermometer-half" style="color:#059e8a;"></i> TEMPERATURE</p><p><span class="reading"><span id="temp">%TEMPERATURE%</span> &deg;C</span></p>
      </div>
      <div class="card">
        <p><i class="fas fa-tint" style="color:#00add6;"></i> HUMIDITY</p><p><span class="reading"><span id="hum">%HUMIDITY%</span> &percnt;</span></p>
      </div>
      <div class="card">
        <p><i class="fas fa-angle-double-down" style="color:#e1e437;"></i> PRESSURE</p><p><span class="reading"><span id="pres">%PRESSURE%</span> hPa</span></p>
      </div>
    </div>
  </div>
<script>
if (!!window.EventSource) {
 var source = new EventSource('/events');
 
 source.addEventListener('open', function(e) {
  console.log("Events Connected");
 }, false);
 source.addEventListener('error', function(e) {
  if (e.target.readyState != EventSource.OPEN) {
    console.log("Events Disconnected");
  }
 }, false);
 
 source.addEventListener('message', function(e) {
  console.log("message", e.data);
 }, false);
 
 source.addEventListener('temperature', function(e) {
  console.log("temperature", e.data);
  document.getElementById("temp").innerHTML = e.data;
 }, false);
 
 source.addEventListener('humidity', function(e) {
  console.log("humidity", e.data);
  document.getElementById("hum").innerHTML = e.data;
 }, false);
 
 source.addEventListener('pressure', function(e) {
  console.log("pressure", e.data);
  document.getElementById("pres").innerHTML = e.data;
 }, false);
}
</script>
</body>
</html>)rawliteral";