Scrivi il firmware di un dispositivo AP con interfaccia web

In questo tutorial metterai in pratica quanto ipotizzato nel precedente tutorial e scriverai uno sketch per implementare un semplice web server che ti servirà come interfaccia per controllare da un browser su PC o dispositivo smart il LED saldato sulla scheda Arduino.



Lo sketch presentato nel video è riportato nel riquadro seguente, da cui puoi copiarlo e incollarlo nell’IDE, apportando le tue eventuali personalizzazioni.

#include <WiFiNINA.h>

char ssid[] = "Arduino Unchained";        // SSID della rete wifi che voglio creare
char pass[] = "unchained";    // Password per la rete

const int led =  LED_BUILTIN; // Parametrizzazione del PIN a cui è collegato il LED
int status = WL_IDLE_STATUS;  // Variabile globale per memorizzare lo stato della connessione wifi

WiFiServer server(80);  // Creo un server TCP che risponde sulla porta 80 (HTTP) - non viene avviato qui

void setup() {
  //Inizializzo la comunicazione seriale con il PC e attendo l'apertura del terminale
  Serial.begin(9600);
  while (!Serial) {
    ; // Ciclo senza fare nulla in attesa che la connessione sulla linea USB venga attivata
  }

  Serial.println("Access Point Web Server");

  pinMode(led, OUTPUT);      // imposto il PIN del LED in modalità OUTPUT

  // Verifico che la scheda abbia effettivamente il modulo WiFi
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Non è presente, o funzionante, il modulo wifi!");
    // Ciclo infinito per bloccare il programma
    while (true);
  }

  // Verifico che la scheda abbia il firmware aggiornato rispetto alla libreria WiFi che sto usando
  String fv = WiFi.firmwareVersion();
  if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
    Serial.println("Il firmware della scheda non è aggiornato!");
  }

  // Stampo il nome della rete (SSID);
  Serial.print("Creo un Access Point con con nome: ");
  Serial.println(ssid);

  // Creo una rete wifi con il SSID scelto e con sicurezza WPA/WPA2 impostando la password
  status = WiFi.beginAP(ssid, pass);
  if (status != WL_AP_LISTENING) {
    Serial.println("Errore nella creazione dell'Access Point");
    Serial.println(status);
    // Ciclo infinito per bloccare il programma
    while (true);
  }

  // avvio il server in ascolto sulla porta 80
  server.begin();

  // Stampo lo stato della connessione
  printWiFiStatus();
}


void loop() {
  // comparo lo stato precedente con quello corrente
  if (status != WiFi.status()) {
    // lo stato è cambiato e aggiorno il valore della variabile globale
    status = WiFi.status();

    if (status == WL_AP_CONNECTED) {
      // Un device si è connesso all'AP
      Serial.println("Device connesso all'AP");
    } else {
      // un device si è disconnesso dall'AP, e siamo nuovamente in modalità di ascolto
      Serial.println("Device disconnesso dall'AP");
    }
  }

  WiFiClient client = server.available();   // creo l'oggetto client - Se non ci sono connessioni l'oggetto viene valutato come FALSE

  if (client) {                             // se l'oggetto client è un client effettivo,
    Serial.println("Connesso un nuovo client");           // stampo un messaggio sulla linea seriale
    String currentLine = "";                // creao una stringa per memorizzare i dati ricevuti dal client
    while (client.connected()) {            // ciclo finché il client è connesso
      if (client.available()) {             // se ci sono byte di dati ricevuti dal client,
        char c = client.read();             // leggo un byte,
        Serial.write(c);                    // stampo il byte sulla linea seriale
        if (c == 'n') {                    // se il byte rappresenta il carattere newline
          // se la linea corrente è blank, abbiamo ricevuto un newline in una riga singola.
          // ciò indica la fine della richiesta HTTP del client, e dobbiamo inviare una risposta:
          if (currentLine.length() == 0) {
            // l'HTTP header deve iniziare sempre con un codice di risposta (es. HTTP/1.1 200 OK)
            // e con un content-type in modo che il client sappia cosa sta per arrivare, seguito da una linea vuota (blank):
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            // quindi procediamo ad inviare il contenuto della risposta:
            generateHTMLPage(client);
            // La risposta HTTP termina con una linea blank:
            client.println();
            // uscita dal loop while:
            break;
          }
          else {      // se abbiamo ricevuto un newline, svuotiamo la linea corrente:
            currentLine = "";
          }
        }
        else if (c != 'r') {    // se abbiamo ricevuto qualunque cosa diversa da un carriage return,
          currentLine += c;      // lo aggiungiamo in coda alla linea corrente
        }
        // Verifichiamo se la richiesta del client è "GET /H" o "GET /L":
        if (currentLine.endsWith("GET /H")) {
          digitalWrite(led, HIGH);               // GET /H accende il LED
        }
        if (currentLine.endsWith("GET /L")) {
          digitalWrite(led, LOW);                // GET /L spegne il LED        
          }
      }
    }
    // chiudiamo la connessione:
    client.stop();
    Serial.println("Client disconnesso");
  }
}

void printWiFiStatus() {
  // stampiamo il SSID della rete wifi:
  Serial.print("SSID: ");
  Serial.println(WiFi.SSID());

  // stampiamo l'IP address del dispositivo:
  IPAddress ip = WiFi.localIP();
  Serial.print("IP Address: ");
  Serial.println(ip);

  // stampiamo una linea di aiuto:
  Serial.print("Per visualizzare la pagina in azione, digita in un browser http://");
  Serial.println(ip);

}

void generateHTMLPage(WiFiClient client){
client.print("<!DOCTYPE html>");
client.print("<head>");
client.print("    <title>Web Server Arduino Unchained</title>");
client.print("</head>");
client.print("<body>");
client.print("    <h1>Benvenuto al Web Server Arduino Unchained!</h1>");
client.print("    <p>Questa e' la pagina web generata dal tuo <b>Arduino</b></p>");
client.print("    <p>Attraverso i link riportati sotto, puoi controllare il LED</p>");
client.print("    <br>");
client.print("    <br>");
client.print("    <a href="/H">Clicca qui</a> per accendere il LED<br>");
client.print("    <a href="/L">Clicca qui</a> per spegnere il LED<br>");
client.print("</body>");
client.print("</html>");

}

Il programma è stato ampiamente commentato e descritto nel video, inoltre, all’interno del codice sono presenti numerosi commenti che ti aiutano a comprendere il significato e il ruolo di ogni linea di codice.

Per tua maggiore comodità riportiamo di seguito una descrizione dettagliata, blocco dopo blocco.

Non è necessario comprendere il significato di ogni singola istruzione. Utilizzando l’intuito puoi facilmente modificare lo sketch per adattarlo alle tue esigenze, per cui se preferisci puoi saltare la spiegazione che segue.

Se ti interessa approfondire qualche nozione di programmazione, l’analisi che segue ti fornirà gli elementi che ti servono per comprendere meglio le singole istruzioni.

Cominciamo dall’inizio del programma.

#include <WiFiNINA.h>

Come già sai, la direttiva “include” chiede al compilatore Arduino di recuperare il file WiFiNINA.h cercandolo nelle cartelle del tuo PC (o nelle cartelle sul server se stai utilizzando l’IDE Arduino via Web) dove è stata installata la libreria WiFiNINA.

Se la libreria non è stata precedentemente installata sul tuo IDE, come correttamente mostrato nel video, al momento della compilazione, riceverai un errore che segnalerà che questo file non è stato trovato.

har ssid[] = "Arduino Unchained";        // SSID della rete wifi che voglio creare
char pass[] = "unchained";    // Password per la rete

Queste due righe definiscono e inizializzano due variabili, chiamate rispettivamente ssid e pass. La coppia di parentesi quadrate poste dopo il nome delle variabili indica che tali variabili sono degli array. Il tipo della variabile è in entrambi i casi char.

Un array è un tipo di dato costituito da una sequenza di valori di un determinato tipo elementare. L’eventuale numero tra parentesi quadre, dopo il nome della variabile, se presente, stabilisce la dimensione dell’array. Nel nostro caso, poiché viene utilizzato immediatamente l’operatore di assegnazione (cioè il segno ‘=‘), la dimensione degli array viene automaticamente calcolata dalla lunghezza della stringa assegnata all’array in fase di inizializzazione.

Nel linguaggio standard C/C++, una stringa, cioè una sequenza di caratteri (char) come questa: “Arduino Unchained”, può essere rappresentata attraverso un array di char. Nell’array vengono inseriti tutti i caratteri della stringa, in realtà vengono inseriti i numeri corrispondenti ai codici ASCII dei singoli caratteri, e, in più, alla fine dell’array viene inserito il valore zero, che rappresenta un carattere “speciale” chiamato “null terminator“.

Il “null terminator” segnala la fine della stringa.

Il C++ ha una serie di funzioni standard che permettono di lavorare con le stringhe, cioè con sequenze di caratteri di lunghezza variabile, e la convenzione di porre il “null terminator” alla fine della stringa consente a queste funzioni di funzionare correttamente.

Pertanto, una istruzione come char pass[] = “unchained” ha l’effetto di creare un array di char di lunghezza pari a 10 caratteri (i nove caratteri della stringa unchained + il carattere null terminator) e di inserire nell’array i codici ASCII di tutti i caratteri della parola unchained, aggiungendo il valore 0 alla fine.

Il linguaggio C/C++ gestisce le stringhe in modo molto tecnico. In istruzioni come quelle che abbiamo appena incontrato le stringhe vengono gestite ad alto livello e la rappresentazione della stringa come sequenza di caratteri racchiusa tra doppi apici è sintetica e facile da comprendere e utilizzare, ma non sempre le cose sono così semplici.

Poiché le stringhe sono un tipo di dato molto utilizzato e molto utile, nel progetto Arduino è stata costruita una libreria di più alto livello che permette di definire e gestire stringhe utilizzando una apposita classe. Un esempio del suo utilizzo lo troveremo più avanti analizzando il nostro codice.

In queste prime righe abbiamo utilizzato i tipi di dato nativi “array di char” perché la libreria WiFiNINA richiede questo tipo di dato nelle sue funzioni.

Le due variabili ssid e password parametrizzano il nome della rete WiFi e la password di accesso che abbiamo definito per il nostro Access Point.

Se vuoi modificare il nome della rete e la password di accesso ti sarà sufficiente modificare queste due righe in testa al programma. Ricorda che per lo standard WiFi con sicurezza WPA/WPA2, sia il ssid che la password devono essere lunghe almeno 8 caratteri.

const int led =  LED_BUILTIN; // Parametrizzazione del PIN a cui è collegato il LED
int status = WL_IDLE_STATUS;  // Variabile globale per memorizzare lo stato della connessione wifi

Queste due righe definiscono altre due variabili globali utilizzate rispettivamente per definire il numero del pin digitale a cui è collegato il LED che vogliamo controllare e lo stato corrente della rete WiFi gestita dalla scheda Arduino.

La variabile led è stata definita come variabile di tipo intero costante, poiché non viene modificata dal programma, ed è stata inizializzata con il valore contenuto nella MACRO LED_BUILTIN.

Quando esegui il compilatore Arduino, LED_BUILTIN viene sostituito con il numero del PIN su cui è saldato il led sulla scheda Arduino.

Se vuoi pilotare un LED esterno collegato ad un altro PIN digitale, puoi inserire al posto di LED_BUILTIN direttamente il numero del PIN da te scelto (ad esempio se il led è collegato al PIN 3, l’istruzione diventa const int led = 3;).

Anche la variabile status è stata dichiarata di tipo intero, ma non è una costante perché sarà modificata durante l’esecuzione del programma.

Questa variabile sarà utilizzata per contenere il codice numerico restituito dalle funzioni della libreria WiFiNINA a fronte di operazioni di impostazione.

Per conoscere il valore corrente dello stato della rete, è sempre possibile invocare direttamente la funzione WiFi.Status(). I codici relativi ai valori di stato della libreria WiFiNINA non sono stati esplicitamente documentati da Arduino. Per ciascun valore è stata definita una MACRO il cui nome è maggiormente parlante.

La variabile status è stata inizializzata con il valore numerico corrispondente definito dalla MACRO WL_IDLE_STATUS. Non è necessario né utile conoscere esattamente quale sia il numero intero definito attraverso questa MACRO poiché potrebbe essere modificato in versioni successive della libreria.

Quando gli autori di una libreria definiscono codici o altri valori costanti mediante MACRO, anziché documentarli e lasciarli utilizzare liberamente dai programmatori, lo fanno per mantenere lo sviluppo e l’evoluzione della libreria non vincolato a scelte che potrebbero rivelarsi riduttive. Usando le MACRO gli autori della libreria saranno liberi di modificare i codice numerici in futuro e il nostro software continuerà a funzionare perché nelle nuove versioni della libreria le MACRO saranno state aggiornate con i nuovi valori corretti.

Nella documentazione della libreria WiFiNINA, che si trova sul sito di Arduino, viene riportato il significato di tutte le MACRO che definiscono codici di stato.

In particolare il valore che abbiamo utilizzato per inizializzare la variabile status è così documentato:

WL_IDLE_STATUS: it is a temporary status assigned when WiFi.begin() is called and remains active until the number of attempts expires (resulting in WL_CONNECT_FAILED) or a connection is established (resulting in WL_CONNECTED);

L’ultima istruzione presente nella parte iniziale del nostro programma è:

WiFiServer server(80);  // Creo un server TCP che risponde sulla porta 80 (HTTP) - non viene avviato qui

Questa istruzione crea un oggetto che abbiamo chiamato server, appartenente alla classe WiFiServer. Abbiamo già incontrato classi e oggetti nei precedenti tutorial.

La classe WiFiServer è definita dalla libreria WiFiNINA.

Al costruttore dell’oggetto viene passato il valore 80.

Questa istruzione crea la struttura dati corrispondente ad un servizio di rete che risponde, cioè gestisce, connessioni logiche sul protocollo TCP sulla porta (TCP PORT) 80.

La libreria WiFiNINA permette infatti di definire servizi server sul protocollo TCP.

Vediamo ora la funzione setup.

void setup() {
  //Inizializzo la comunicazione seriale con il PC e attendo l'apertura del terminale
  Serial.begin(9600);
  while (!Serial) {
    ; // Ciclo senza fare nulla in attesa che la connessione sulla linea USB venga attivata
  }
...

La prima cosa che viene fatta nella funzione setup, è l’inizializzazione della connessione seriale verso il PC con velocità impostata a 9600 bit/s.

Abbiamo già detto in un precedente tutorial che Serial è una classe predefinita in Arduino.

L’oggetto Serial assume valore zero se nessun dispositivo è connesso sulla linea RS232. Finché non viene aperto il terminale seriale sul PC, il valore di Serial è zero.

L’istruzione while(!Serial) cicla all’infinito, senza fare nulla finché il valore di Serial resta zero. Non appena il terminale seriale viene aperto, Serial assume valore diverso da zero !Serial viene valutato come FALSO e l’istruzione while termina consentendo la prosecuzione del programma.

Serial.println("Access Point Web Server");

pinMode(led, OUTPUT);      // imposto il PIN del LED in modalità OUTPUT

La funzione setup procede stampando un titolo sul terminale seriale e impostando il PIN del led in modalità OUTPUT.

// Verifico che la scheda abbia effettivamente il modulo WiFi
  if (WiFi.status() == WL_NO_MODULE) {
    Serial.println("Non è presente, o funzionante, il modulo wifi!");
    // Ciclo infinito per bloccare il programma
    while (true);
  }

Viene quindi richiamata la funzione WiFi.status() all’interno di una istruzione if per verificare se per caso lo stato corrente segnali la mancanza o il malfunzionamento del modulo WiFi. Il codice di stato è identificato tramite la MACRO WL_NO_MODULE definita dalla libreria WiFi NINA.

Nel caso in cui il test fornisca esito positivo l’esecuzione del programma viene bloccata con la consueta istruzione di ciclo infinito while(true).

Se invece il test fallisce, l’esecuzione prosegue con il prossimo blocco di istruzioni che verifica l’eventuale disallineamento tra la versione di firmware presente sul dispositivo WiFi e la versione della libreria WiFi NINA che stiamo usando.

// Verifico che la scheda abbia il firmware aggiornato rispetto alla libreria WiFi che sto usando
  String fv = WiFi.firmwareVersion();
  if (fv < WIFI_FIRMWARE_LATEST_VERSION) {
    Serial.println("Il firmware della scheda non è aggiornato!");
  }

In caso di disallineamento viene stampato un messaggio di avviso sul terminale ma l’esecuzione comunque non viene bloccata, poiché il sistema potrebbe comunque funzionare.

// Stampo il nome della rete (SSID);
  Serial.print("Creo un Access Point con con nome: ");
  Serial.println(ssid);

  // Creo una rete wifi con il SSID scelto e con sicurezza WPA/WPA2 impostando la password
  status = WiFi.beginAP(ssid, pass);
  if (status != WL_AP_LISTENING) {
    Serial.println("Errore nella creazione dell'Access Point");
    Serial.println(status);
    // Ciclo infinito per bloccare il programma
    while (true);
  }

Sul terminale seriale vengono stampate le informazioni sulla rete wifi che stiamo per creare e viene quindi richiamata la funzione WiFi.beginAP.

Alla funzione vengono passati due parametri. Sono i due array di char che abbiamo definito in testa al programma, contenenti il nome della rete e la password da impostare.

Questa chiamata causa l’impostazione del dispositivo wifi come Access Point con sicurezza WPA/WPA2.

il valore restituito dalla WiFibeginAP è un codice di stato che viene quindi memorizzato nella variabile globale status. Questo è il nuovo stato corrente della rete.

Le istruzioni successive verificano che lo stato corrente sia quello definito dalla MACRO WL_AP_LISTENING e in caso contrario l’esecuzione del programma viene bloccata.

Se tutto è andato bene e la rete si trova in stato di attesa di connessioni (listening) l’esecuzione prosegue e viene attivato il servizio di ascolto sulla porta 80 del protocollo TCP, richiamando la funzione begin dell’oggetto server che avevamo definito e creato nella sezione delle variabili globali.

 // avvio il server in ascolto sulla porta 80
  server.begin();

  // Stampo lo stato della connessione
  printWiFiStatus();
}

La funzione printWiFiStatus() raggruppa alcune stampe sul terminale seriale ed è definita alla fine del nostro sketch.

A questo punto parte la funzione loop.

void loop() {
  // comparo lo stato precedente con quello corrente
  if (status != WiFi.status()) {
    // lo stato è cambiato e aggiorno il valore della variabile globale
    status = WiFi.status();

    if (status == WL_AP_CONNECTED) {
      // Un device si è connesso all'AP
      Serial.println("Device connesso all'AP");
    } else {
      // un device si è disconnesso dall'AP, e siamo nuovamente in modalità di ascolto
      Serial.println("Device disconnesso dall'AP");
    }
  }

Nella prima parte di codice viene chiamata la funzione WiFiStatus e comparato lo stato restituito con lo stato precedentemente memorizzato nella variabile globale status.

Se i due valori differiscono, vuol dire che qualcosa è cambiato. Se eravamo in attesa di connessioni e ora esiste un dispositivo WiFi connesso al nostro AP stampiamo un messaggio informativo sul terminale.

Analogamente se passiamo dallo stato di connessione allo stato di listening vuol dire che il dispositivo connesso si è disconnesso e quindi stampiamo il relativo messaggio informativo.

Ed ora arriva la parte più complessa del codice, quella che implementa il protocollo HTTP e identifica l’eventuale URL GET /L o GET /H che segnala i comandi di accensione o spegnimento del LED.

WiFiClient client = server.available();   // creo l'oggetto client - Se non ci sono connessioni l'oggetto viene valutato come FALSE

  if (client) {                             // se l'oggetto client è un client effettivo,
    Serial.println("Connesso un nuovo client");           // stampo un messaggio sulla linea seriale
    String currentLine = "";                // creao una stringa per memorizzare i dati ricevuti dal client
    while (client.connected()) {            // ciclo finché il client è connesso
      if (client.available()) {             // se ci sono byte di dati ricevuti dal client,
        char c = client.read();             // leggo un byte,
        Serial.write(c);                    // stampo il byte sulla linea seriale
        if (c == 'n') {                    // se il byte rappresenta il carattere newline
          // se la linea corrente è blank, abbiamo ricevuto un newline in una riga singola.
          // ciò indica la fine della richiesta HTTP del client, e dobbiamo inviare una risposta:
          if (currentLine.length() == 0) {
            // l'HTTP header deve iniziare sempre con un codice di risposta (es. HTTP/1.1 200 OK)
            // e con un content-type in modo che il client sappia cosa sta per arrivare, seguito da una linea vuota (blank):
            client.println("HTTP/1.1 200 OK");
            client.println("Content-type:text/html");
            client.println();
            // quindi procediamo ad inviare il contenuto della risposta:
            generateHTMLPage(client);
            // La risposta HTTP termina con una linea blank:
            client.println();
            // uscita dal loop while:
            break;
          }
          else {      // se abbiamo ricevuto un newline, svuotiamo la linea corrente:
            currentLine = "";
          }
        }
        else if (c != 'r') {    // se abbiamo ricevuto qualunque cosa diversa da un carriage return,
          currentLine += c;      // lo aggiungiamo in coda alla linea corrente
        }
        // Verifichiamo se la richiesta del client è "GET /H" o "GET /L":
        if (currentLine.endsWith("GET /H")) {
          digitalWrite(led, HIGH);               // GET /H accende il LED
        }
        if (currentLine.endsWith("GET /L")) {
          digitalWrite(led, LOW);                // GET /L spegne il LED        
          }
      }
    }
    // chiudiamo la connessione:
    client.stop();
    Serial.println("Client disconnesso");
  }
}

Viene dapprima creato l’oggetto client invocando la funzione server.available().

Se un browser web si è connesso al server (porta 80 del protocollo TCP), la funzione server.available() restituisce il riferimento ad un nuovo oggetto che contiene dati e funzioni per gestire la connessione TCP.

Se nessun browser web è connesso, l’oggetto client vale zero (equivalente al valore logico FALSO).

Tutto il resto delle istruzioni è contenuto nel blocco di una istruzione if che valuta il valore di client.

Se client è zero, quindi logicamente il suo valore è FALSO, l’intero blocco viene saltato e il ciclo loop ricomincia da capo.

Se invece esiste una connessione al server, client ha un valore diverso da zero e viene eseguito il codice nel blocco.

Per prima cosa viene creato un oggetto della classe String. Si tratta di una classe standard delle librerie Arduino che offre funzioni molto utili per gestire stringhe di testo, come ad esempio la funzione lenght() e la funzione endsWith() che usiamo in questo sketch.

Abbiamo chiamato questo oggetto currentLine e lo abbiamo inizializzato come stringa vuota “”.

Quando un browser è connesso al server, la funzione client.connected() dell’oggetto client restituito da server.available() restituisce un valore logico VERO.

Il browser avrà inviato il suo messaggio nel protocollo HTTP al server e tale messaggio sarà contenuto nel buffer di ricezione del server TCP.

La connessione, identificata dall’oggetto client, rimarrà attiva finché non chiameremo esplicitamente la funzione client.stop().

Il codice contiene un ciclo, realizzato mediante una istruzione while che ha come condizione per continuare ad eseguire il suo blocco di codice, l’esito della funzione client.connected().

Finché la connessione con il server sulla porta TCP 80 è attiva, viene verificato se nel buffer del server è presente un carattere da leggere, la funzione client.available() è usata a questo scopo, il carattere viene letto, client.read(), viene inviato al terminale seriale e poi viene verificato se si tratta di un newline.

Se il carattere appena letto è un newline, sono possibili due casi. Il carattere è il primo carattere di una riga del messaggio (la stringa currentLine ha lunghezza zero, currentLine.lenght() restituisce zero), oppure si tratta del carattere di fine riga di una riga che contiene altri caratteri, currentLine.lenght() ha restituito un valore diverso da zero.

Nel primo caso siamo in presenza di una riga vuota e questo indica il termine del messaggio HTTP inviato dal browser. In questo caso il server deve rispondere con il messaggio di OK previsto dal protocollo HTTP e inviare il testo che costituisce la pagina HTML da visualizzare nel browser.

Se invece siamo al termine di una riga, possiamo azzerare la stringa currentLine usata per contenere la riga corrente del messaggio e procedere nel ciclo leggendo la riga successiva estraendo un carattere dopo l’altro dal buffer del server.

Se non abbiamo letto un carattere newline e il carattere non è neanche un ‘r’ (ricordiamo sempre che i browser che girano su S.O. Windows potrebbero inviare il carattere ‘r’ dopo il newline), il carattere viene aggiunto alla stringa currentLine.

Ogni volta che viene aggiunto un nuovo carattere alla currentLine, viene verificato che non sia stata completato il testo “GET /L” oppure “GET /H”, cioè viene verificato che la stringa che stiamo riempiendo con i caratteri letti dal buffer non termini per “GET /L” o per “GET /H”, tramite chiamate alla funzione currentLine.endsWith().

Qualora venga identificata una di queste sequenze, viene acceso o spento il LED.

Qualunque sia stata la richiesta da parte del browser, viene sempre inviata come risposta la sequenza HTTP/1.1 200 OK, Content-type:text/html, seguita dal codice HTML della pagina web presentata nel precedente tutorial.

Abbiamo raggruppato la scrittura sulla connessione TCP del codice HTML in una funzione chiamata generateHTMLPage();

void generateHTMLPage(WiFiClient client){
client.print("<!DOCTYPE html>");
client.print("<head>");
client.print("    <title>Web Server Arduino Unchained</title>");
client.print("</head>");
client.print("<body>");
client.print("    <h1>Benvenuto al Web Server Arduino Unchained!</h1>");
client.print("    <p>Questa e' la pagina web generata dal tuo <b>Arduino</b></p>");
client.print("    <p>Attraverso i link riportati sotto, puoi controllare il LED</p>");
client.print("    <br>");
client.print("    <br>");
client.print("    <a href="/H">Clicca qui</a> per accendere il LED<br>");
client.print("    <a href="/L">Clicca qui</a> per spegnere il LED<br>");
client.print("</body>");
client.print("</html>");

}

A questa funzione deve essere passato come parametro l’oggetto client che contiene i riferimenti alla connessione TCP.

Si noti che il codice HTML contiene dei caratteri doppio apice, che fanno parte di ciò che dobbiamo inviare. Poiché la sintassi della funzione client.print(), prevede che la sequenza di caratteri da inviare sia racchiusa tra doppi apici, i doppi apici che fanno parte del testo HTML interferirebbero con i doppi apici delimitatori della sequenza. Questo problema si risolve indicando i doppi apici che fanno parte del testo da inviare con la sequenza ‘ ” ‘. L’uso del carattere , chiamato carattere di escape, informa il compilatore che il carattere che segue è un carattere che fa parte del testo della stringa e non un delimitatore della stringa stessa.