ESP32 e caricamento pagina web

Ciao a tutti,
Ho una ESP32 che funziona benissimo.

Ora vorrei però far sì che, durante lo svolgimento di una funzione, aggiorni la pagine web prima che la funzione si concluda senza passare dal loop. Sapete come aiutarmi? Allego codice di interesse:

                  statoSlot = (F("<button> In carica </a></button>")); //non viene mai visualizzato
                  client.println(statoSlot);
                  Serial.println(statoSlot);
                  delay (20000);
                  statoSlot = (F("<button> Slot 1 pieno </a></button>")); 
                  Serial.println(statoSlot);
                  Serial.println();
                  return; //viene visualizzato "Slot 1 pieno" terminata la funzione

Ciao

non pubblicare frammenti (Snippets R Us!)

ritardare è una cattiva idea, non saprai cosa succede. La connessione deve essere chiusa una volta inviata la risposta e il client non è più connesso

studia questo esempio

Innanzitutto grazie della risposta,
non mando il codice perchè è veramente lungo e, sotto molti punti di vista imbarazzante (sono un noobie).

Provo a spiegare qual è il problema e, in caso, lo invio:

Ho la funzione A e B che fa determinate cose (comanda i servo, legge i dati etc, client.print) e la funzione C che raccoglie tutti i client.print da inviare ad un sito esterno. Il sito esterno permette di avviare le funzioni (A o B) in base a cosa gli dice C.

E fin qua tutto ok.

In alcune situazioni però, ho bisogno che il sito esterno legga i vari client.print che gli dice C prima che la funzione A o B termini ma, C non dice nulla prima prima del completamento dell’esecuzione (infatti se provo a caricare C per leggere i dati, carica la pagina fino al completamento della funzione A o B). Non penso ci sia una soluzione valida al mio problema e devo rivedere tutto, però ci provo…

Le funzioni si avviano andando su IP/A e IP/B mentre i dati sono leggibili su IP/C

senza il codice è difficile …

Ok, ecco il codice, spero si capisca qualcosa…

#include <WiFi.h>
#include <WiFiClient.h>
#include <WebServer.h>
#include <ESPmDNS.h>
#include <ESP32Servo.h>
#include <SPI.h>
#include <MFRC522.h>
#include <Stepper.h>
Servo servo;

//WIFI
#ifndef STASSID
#define STASSID "*****"
#define STAPSK  "***"
#endif

const char* ssid = STASSID;
const char* password = STAPSK;
String statoSlot;
String statohtml;
String selfClose;
int statoRFID;
String ritiraSuccesso;
WebServer server(80);


//RFID
#define SS_PIN 21
#define RST_PIN 22
MFRC522 mfrc522(SS_PIN, RST_PIN);   // Create MFRC522 instance.


// LED
#define PinBianco 14
#define PinRosso 27
#define PinVerde 26


// Stepper
#define stepPin 17
#define dirPin 16
#define enPin 32



void setup(void) {
WiFiClient client;
  Serial.begin(9600);
  WiFi.mode(WIFI_STA);
  WiFi.begin(ssid, password);
  Serial.println("");
  servo.attach(2);
  SPI.begin();                                                  // Init SPI bus
  mfrc522.PCD_Init();                                              // Init MFRC522 card
  pinMode(stepPin, OUTPUT);
  pinMode(dirPin, OUTPUT);
  pinMode(enPin, OUTPUT);
  pinMode (33, OUTPUT);
  digitalWrite(enPin, LOW);

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

  if (MDNS.begin("XX")) {
    Serial.println("MDNS responder started");
  }

  if (digitalRead(5) == HIGH && mfrc522.PICC_IsNewCardPresent()){
    statoSlot = (F("<button class= 'errore' onclick= 'ritiraSlot1();'> ERRORE - Slot pieno </a></button>"));
    Serial.println(statoSlot);
     }
   else if (digitalRead(5) == HIGH && ! mfrc522.PICC_IsNewCardPresent()){
    statoSlot = (F("<button class= 'ritira' onclick= 'ritiraSlot1();'> Slot 1 vuoto </a></button>"));
    Serial.println(statoSlot);
     }
   else if (digitalRead(5) == LOW && mfrc522.PICC_IsNewCardPresent())
   {   statoSlot = (F("<button class= 'consegna' onclick= 'consegnaSlot1();'> Slot 1 pieno </a></button>"));
   Serial.println(statoSlot);
   }
   else if (digitalRead(5) == LOW && ! mfrc522.PICC_IsNewCardPresent())
   {   statoSlot = (F("<button class= 'errore' onclick= 'consegnaSlot1();'> ERRORE - Slot vuoto </a></button>"));
   Serial.println(statoSlot);
   }



  server.on("/stato",HTTP_GET, []() {
    server.sendHeader("Access-Control-Max-Age", "10000");
    server.sendHeader("Access-Control-Allow-Methods", "POST,GET,OPTIONS");
    server.sendHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
    server.sendHeader("Access-Control-Allow-Origin","*");
    server.send(200, "text/html", statoSlot );
  });
  
   server.on("/acquisto", acquisto); 

   server.on("/ritira", ritira); 

  server.on("/exit", uscita);

  server.on("/pausa", pausa);

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


void acquisto() {
WiFiClient client;

  servo.attach(2);
  pinMode(5, INPUT);
  pinMode (PinBianco, OUTPUT);
  pinMode (PinRosso, OUTPUT);
  pinMode (PinVerde, OUTPUT);
  digitalWrite(33, HIGH);
  MFRC522::MIFARE_Key key;
  MFRC522::StatusCode status;
  SPI.begin();                                                  // Init SPI bus
  mfrc522.PCD_Init();      
  int j = 0;

 /*******************************************************
 *  CHIUDE LA PAGINA DOPO 5 SECONDI
 ******************************************************/
  
 server.sendHeader("connection", "keep-alive");
  server.sendHeader("Access-Control-Allow-Origin","*");
  server.send(200, "text/html", "<html>\
  <head>\
  <script>\
  setTimeout('self.close()', 5000);\
  </script>\
  <body onload = setTimeout(self.close(), 5000)>\
  </body>\
  ");            

//  ritiraSuccesso = (F(""));

  if (digitalRead(5) == LOW and mfrc522.PICC_IsNewCardPresent() and mfrc522.PICC_ReadCardSerial()) //Legge RFID e sensore. se ok procede
    { digitalWrite(PinBianco, LOW);
      digitalWrite(PinVerde, HIGH);

        for (byte i = 0; i < mfrc522.uid.size; i++) 
          {
            Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : "");
               Serial.print(mfrc522.uid.uidByte[i], HEX);
                }
                  Serial.println();


    delay(10);
      delay(300);
        servo.write(0);
          delay(3000);

    digitalWrite(dirPin, HIGH); //STEPPER
    
    for (int x = 0; x < 800; x++) {
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(800);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(800);
    }
    delay(1000); // One second delay

    digitalWrite(dirPin, LOW); 
    for (int x = 0; x < 800; x++) {
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(800);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(800);  //STEPPER
    }

  int detect = digitalRead(5);
    bool USCITO = true;

/*******************************************************
 *  aspetto che il dispositivo esca
 ******************************************************/
    while (USCITO) {
      if (digitalRead(5) == HIGH) {
        USCITO = false;
          delay(2000);
            servo.write(90);
              digitalWrite(PinBianco, HIGH);
                digitalWrite(PinVerde, LOW);
             }
           }

           
statoSlot = (F("<button class= 'ritira' onclick= 'ritiraSlot1();'> Slot 1 vuoto </a></button>"));
  client.println(statoSlot);
    Serial.println(statoSlot); 
    
  }

  
  else { //se RFID no e sensore no allora non disp
    Serial.println ("NON DISPONIBILE");
       statoSlot = (F("<button class= 'errore' onclick= 'ritiraSlot1();'> ERRORE </a></button>"));
        client.println(statoSlot);
          Serial.println(statoSlot);
               statohtml = (F("<div class='problema'> ATTENZIONE! Riprovare </div>\
               <div class=bottoni>\
            <form action='http://XXXXX/XX.html' method='get' target='_self'>\
        <button class='esci' href='http://XXXXX/XX.html'> RIPROVA </button>\
        </form>\
        </div>\               
               "));


            delay(2000);
         }
   while (digitalRead(5) == LOW)
  {j++;
  delay(1000);
  Serial.println(j);
  if (j==15 && ! mfrc522.PICC_IsNewCardPresent() ) {
   Serial.println("ritiraRE DISPOSITIVO");
  }
  if (j==30 && ! mfrc522.PICC_IsNewCardPresent() ) {
   statoSlot = (F("<button> Errore ritira </a></button>"));
   Serial.println(statoSlot);
    servo.write(90);
    delay (1000);
   return;
  }    
}

}

void ritira() {
WiFiClient client;

  digitalWrite(2, LOW);
  servo.attach(12);
  pinMode(5, INPUT);
  pinMode (PinBianco, OUTPUT);
  pinMode (PinRosso, OUTPUT);
  pinMode (PinVerde, OUTPUT);
  digitalWrite(33, HIGH);
  #define VIN 34;
  statoRFID = 0;
  MFRC522::MIFARE_Key key;
  MFRC522::StatusCode status;
  SPI.begin();                                                  // Init SPI bus
  mfrc522.PCD_Init();
  int j = 0;

  /*******************************************************
 *  CHIUDE LA PAGINA DOPO 5 SECONDI
 ******************************************************/
 
  server.sendHeader("connection", "keep-alive");
  server.sendHeader("Access-Control-Allow-Origin","*");
 server.send(200, "text/html", "<html>\
  <head>\
  <script>\
  setTimeout('self.close()', 5000);\
  </script>\
  <body onload = setTimeout(self.close(), 5000)>\
  </body>\
  ");          

// ritiraSuccesso = (F(""));

  if //controllo
  (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial()) {
    (statoRFID == 0);
  }
  else {
    (statoRFID == 1);
  }

  if (digitalRead(5) == LOW)
  {
    statoSlot = (F("<button class= 'consegna' onclick= 'consegnaSlot1();'> Slot 1 pieno </a></button>"));
      client.println(statoSlot);
        Serial.println(statoSlot);
          Serial.println ("NON DISPONIBILE");
             statohtml = (F("<div class='problema'> ATTENZIONE! Riprovare </div>\
               <div class=bottoni>\
            <form action='http://XXXXX/XX.html' method='get' target='_self'>\
        <button class='esci' href='http://XXXXX/XX.html'> RIPROVA </button>\
        </form>\
        </div>\               
               "));

            delay(2000);
       }

else { //apre il vetro, accende i led e legge la carica
  
    digitalWrite(PinVerde, HIGH);
    digitalWrite(PinBianco, LOW);
      float average = 0;
        for (int i = 0; i < 1000; i++) {
        average = average + (.0264 * analogRead(34) - 13.51) / 1000;
        delay(0);
      }
    delay(300);
      Serial.println(average);
        servo.write(0);

/*******************************************************
 *    HA 30 SECONDI PER INSERIRE  IL DISPOSITIVO
 ******************************************************/
 

 while (digitalRead(5) == HIGH)
  {j++;
  delay(1000);
  Serial.println(j);
  if (j==15 && ! mfrc522.PICC_IsNewCardPresent() ) {
   Serial.println("INSERIRE DISPOSITIVO");
  }
  if (j==30 && ! mfrc522.PICC_IsNewCardPresent() ) {
   statoSlot = (F("<button> NON è STATO INSERITO IL DISPOSITIVO </a></button>"));
   Serial.println(statoSlot);
    servo.write(90);
    statoSlot = (F("<button class= 'ritira' onclick= 'ritiraSlot1();'> Slot 1 vuoto </a></button>"));
    delay (1000);
   return;
  }
  else if (j==30 && mfrc522.PICC_IsNewCardPresent()) {
     statoSlot = (F("<button> ERRORE PROSSIMITà </a></button>"));
     Serial.println(statoSlot);
    servo.write(90);
    delay (1000);
   return;
  }
  }
  int j=0;

/*******************************************************
 *  LETTURA CORRENTE
 ******************************************************/          

   delay(1000);

    float average1 = 0;
        for (int i = 0; i < 1000; i++) {
        average1 = average1 + (.0264 * analogRead(34) - 13.51) / 1000;
        delay(0);
      }
       delay(300);
       
      Serial.println(average1);
      delay(300);

 /*******************************************************
 *  LETTURA DI TUTTI I VALORI
 ******************************************************/
 

      if
      (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial() && average1 > average && digitalRead(5) == LOW ) {
       for (byte i = 0; i < mfrc522.uid.size; i++) 
  {
     Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : "");
     Serial.print(mfrc522.uid.uidByte[i], HEX);

  }
    Serial.println("");   
    delay (2000);
    servo.write(90);
    delay (1000);
    return pausa();
      }

 /*******************************************************
 *  SE CORRENTE è INFERIORE
 ******************************************************/

 else if
      (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial() && average1 < average && digitalRead(5) == LOW ) {
       for (byte i = 0; i < mfrc522.uid.size; i++) 
  {
     Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : "");
     Serial.print(mfrc522.uid.uidByte[i], HEX);

  }
    Serial.println("");
    delay (2000);
    servo.write(90);
    statoSlot = (F("<button> Errore Corrente </a></button>"));
    Serial.println(statoSlot);
    delay (1000);
    return;
      }

 /*********************************************************************************
 *  SE IL DISPOSITIVO è STATO INSERITO AL CONTRARIO, FA USCIRE IL DISPOSITIVO
 **********************************************************************************/

  else if ( ! mfrc522.PICC_IsNewCardPresent() && average1 > average && digitalRead(5) == LOW ) {
          Serial.println("INSERIRE DISPOSITIVO CORRETTAMENTE");
      digitalWrite(PinVerde, LOW);
      digitalWrite(PinRosso, HIGH);
      delay(3000);

      digitalWrite(dirPin, HIGH); // Enables the motor to move in a particular direction
      // Makes 200 pulses for making one full cycle rotation
      for (int x = 0; x < 800; x++) {
        digitalWrite(stepPin, HIGH);
        delayMicroseconds(800);
        digitalWrite(stepPin, LOW);
        delayMicroseconds(800);
      }


      delay(1000);
      Serial.println("INSERIRE NUOVAMENTE");

      digitalWrite(dirPin, LOW); //Changes the rotations direction
      // Makes 400 pulses for making two full cycle rotation
      for (int x = 0; x < 800; x++) {
        digitalWrite(stepPin, HIGH);
        delayMicroseconds(800);
        digitalWrite(stepPin, LOW);
        delayMicroseconds(800);
      }
        }

 while (digitalRead(5) == HIGH)
  {j++;
  delay(1000);
  Serial.println(j);
  if (j==15 && ! mfrc522.PICC_IsNewCardPresent() ) {
   Serial.println("INSERIRE DISPOSITIVO");
  }
  if (j==30 && ! mfrc522.PICC_IsNewCardPresent() ) {
   statoSlot = (F("<button> Non è stato inserito il XX </a></button>"));
   Serial.println(statoSlot);
    servo.write(90);
    delay (1000);
   return;
  }
  else if (digitalRead(5) == LOW && ! mfrc522.PICC_IsNewCardPresent()) {
    delay (2000);
    statoSlot = (F("<button> XX inserito al contrario </a></button>"));
    Serial.println(statoSlot);
    servo.write(90);
    delay (1000);
   return;
  }
  }

        if
      (mfrc522.PICC_IsNewCardPresent() && mfrc522.PICC_ReadCardSerial() && average1 > average && digitalRead(5) == LOW ) {
       for (byte i = 0; i < mfrc522.uid.size; i++) 
  {
     Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : "");
     Serial.print(mfrc522.uid.uidByte[i], HEX);

  }
    Serial.println("");
    delay (2000);
    servo.write(90);
  //   ritiraSuccesso = (F(""));
    delay (1000);
    return pausa();
      }
}
  }


void pausa(){
WiFiClient client;

  digitalWrite(2, LOW);
  servo.attach(12);
  pinMode(5, INPUT);
  pinMode (PinBianco, OUTPUT);
  pinMode (PinRosso, OUTPUT);
  pinMode (PinVerde, OUTPUT);
  digitalWrite(33, HIGH);
  #define VIN 34;
  statoRFID = 0;
  MFRC522::MIFARE_Key key;
  MFRC522::StatusCode status;
  SPI.begin();                                                  // Init SPI bus
  mfrc522.PCD_Init();
             

  digitalWrite(PinRosso, LOW);
  digitalWrite(PinVerde, LOW);
  digitalWrite(PinBianco, HIGH);


statoSlot = (F("<button> DISPOSITIVO in carica </a></button>"));
                  client.println(statoSlot);                  
                  Serial.println(statoSlot);
                  delay (20000);
                  statoSlot = (F("<button class= 'consegna' onclick= 'consegnaSlot1();'> Slot 1 pieno </a></button>"));
                  Serial.println(statoSlot);
                   client.println(statoSlot);
                  Serial.println();
                  return;
}


void uscita() {
WiFiClient client;

  servo.attach(2);
  pinMode(5, INPUT);
  pinMode (PinBianco, OUTPUT);
  pinMode (PinRosso, OUTPUT);
  pinMode (PinVerde, OUTPUT);
  digitalWrite(33, HIGH);
  MFRC522::MIFARE_Key key;
  MFRC522::StatusCode status;
  SPI.begin();                                                  // Init SPI bus
  mfrc522.PCD_Init();      

server.sendHeader("connection", "keep-alive");
  server.sendHeader("Access-Control-Allow-Origin","*");
  server.send(200, "text/html");    
 

        servo.write(0);
        delay(3000);

    digitalWrite(dirPin, HIGH); //STEPPER
    
    for (int x = 0; x < 800; x++) {
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(800);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(800);
    }
    delay(1000); // One second delay

    digitalWrite(dirPin, LOW); 
    for (int x = 0; x < 800; x++) {
      digitalWrite(stepPin, HIGH);
      delayMicroseconds(800);
      digitalWrite(stepPin, LOW);
      delayMicroseconds(800);  //STEPPER
    }

  int detect = digitalRead(5);
    bool USCITO = true;

/*******************************************************
 *  aspetto che il dispositivo esca
 ******************************************************/
    while (USCITO) {
      if (digitalRead(5) == HIGH) {
        USCITO = false;
          delay(2000);
            servo.write(90);
  
}
    }

     statoSlot = (F("<button class= 'ritira' onclick= 'ritiraSlot1();'> Slot 1 vuoto </a></button>"));
        client.println(statoSlot);
          Serial.println(statoSlot);
}

void loop(void) {
  server.handleClient();
  delay(1);

}

String RFID(){
  
  for (byte i = 0; i < mfrc522.uid.size; i++) 
  {
     Serial.print(mfrc522.uid.uidByte[i] < 0x10 ? " 0" : "");
     Serial.print(mfrc522.uid.uidByte[i], HEX);
     Serial.print("");
  }
  Serial.println();
}

Non capisco cosa pensi che la variabile client rappresenti

per esempio

void acquisto() {
  WiFiClient client;  // <<=== VARIABILE LOCALE
 ...

  if (digitalRead(5) == LOW and mfrc522.PICC_IsNewCardPresent() and mfrc522.PICC_ReadCardSerial()) //Legge RFID e sensore. se ok procede
  {
    ...
    statoSlot = (F("<button class= 'ritira' onclick= 'ritiraSlot1();'> Slot 1 vuoto </a></button>"));
    client.println(statoSlot);
    Serial.println(statoSlot);
  } else { //se RFID no e sensore no allora non disp
    Serial.println ("NON DISPONIBILE");
    statoSlot = (F("<button class= 'errore' onclick= 'ritiraSlot1();'> ERRORE </a></button>"));
    client.println(statoSlot);
    Serial.println(statoSlot);
    ...
  }
}

client non è connesso a nulla…

se fai un passo indietro, puoi descrivere cosa dovrebbe fare il programma?

Il programma deve mandare a ip/stato il bottone. Il bottone viene letto tramite script da sito esterno (e questo lo fa). Il problema è quando, ad esempio, void pausa() è in funzione: “dispositivo in carica” non viene mai mostrato sul sito, ma viene mostrato solo “slot pieno” al termine della funzione.

Inoltre quando void pausa() è in funzione, se cerco di caricare IP/stato questo viene caricato solo alla fine della funzione e le richieste (lette da Ispeziona di Chrome) rimangono in “pending”

sembra che tu abbia un malinteso su come funzionano le semplici pagine web. Il browser richiede una pagina, il server risponde e la connessione viene chiusa. Il server a quel punto non ha modo di raggiungere il browser (a meno che non si utilizzino tecniche avanzate specifiche del browser).
Quindi, se vuoi avere un aggiornamento di stato sul lato browser, il browser deve inviare una nuova richiesta. Se vuoi un aggiornamento senza ridisegnare l’intera pagina, puoi esplorare AJAX

Su ESP32 consiglierei ESPAsyncWebServer che ha più potenza delle classi WebServer più semplici.

Non ho letto con attenzione il codice, ma a prima vista ci sono diverse cose che non vanno secondo me.
Come ti ha già detto @J-M-L i diversi WiFiClient client che hai sparpagliato nelle funzioni non servono a nulla. L’oggetto va usato quando l’ESP deve collegarsi ad un server, non viceversa.

Se vuoi che l’ESP risponda con prontezza alle richieste del browser, devi EVITARE cicli bloccanti ed ho visto diversi blocchi while nello sketch.
Insomma per farla breve va rivisto un po’ tutto, ma come prima cosa va rivisto il meccanismo di comunicazione browser/client.

Se vuoi essere in grado di inviare messaggi al browser dal lato server (ovviamente una volta che la pagina è stata aperta), un’ottima scelta è la “tecnologia” WebSocket, ma creare pagine dinamiche componendo a runtime la pagina web con delle String è un suicidio secondo me… :exploding_head:

Visto che stai usando un ESP32 che ha tanta flash disponibile, dai un’occhiata all’esempio incluso FSBrowser.ino

se non vuoi usare AJAX, c’è una biblioteca WebSocket Server and Client for Arduino

Grazie mille sia a te che a @J-M-L

Forse ho fatto il passo più lungo della gamba e dovrò ritornare a studiare per bene. Nel frattempo ho eliminato tutti i vari client in giro.

Ho una sola domanda: è possibile lanciare una funzione da /ritira senza che la connessione con /stato venga interrotta? WebSocket può fare questo?

WebSocket gira in parallelo al webserver, ma sono due cose distinte. E’ una tecnologia che ti consente di instaurare una connessione asincrona bidirezionale attraverso il socket TCP.

Questo significa che lo puoi usare più o meno come se fosse una connessione seriale per capirci; scrivi e leggi sul socket quando e come vuoi. Ovviamente dovrai mettere in piedi un mezzo protocollo per decidere cosa fare in funzione del messaggio che ricevi.

Non è l’unico modo per far dialogare due dispositivi connessi in rete, ma è l’unico che consente di far dialogare in modo bidirezionale browser e dispositivo (da quanto ne so) e ovviamente se non c’è questa necessità non serve, basta fare delle chiamate AJAX come già scritto.

Lato ESP dovrai far partire un server, mentre sul browser devi far girare un client che si mette in ascolto sulla porta indicata e il modo più facile è con Javascipt. Se cerchi online troverai decine e decine di esempi.

come ho detto, il modo tipico in cui funziona è:

  • il browser invia una richiesta al server
  • il server prepara e invia una risposta al browser
  • il browser interpreta l’HTML, generando eventualmente più richieste e “dipinge lo schermo”
  • il browser chiude la connessione

se vuoi aggiornare qualche elemento nella pagina web, il browser deve prendere l’iniziativa in quanto non c’è modo per il server di raggiungere il browser (a meno che tu non abbia usato webscocket, nel qual caso la comunicazione non è stata chiusa), il il browser deve prendere l’iniziativa.

Il tuo Arduino di solito fa 1 cosa alla volta ma funziona velocemente, quindi può servire le richieste web E fare qualcos’altro. Il codice deve solo essere scritto in modo asincrono