ESP32 (8) – connessione tcp

by luca
13 comments

Dopo aver imparato come collegare il chip esp32 alla nostra rete wifi, oggi vediamo come inviare e ricevere dati.

lwIP

Il framework esp-idf utilizza la libreria lwIP per implementare lo stack di protocolli TCP/IP. Questa libreria, inizialmente sviluppata da Adam Dunkels e ora mantenuta da una community di sviluppatori con licenza opensource, è molto utilizzata in ambito embedded per le sue ridotte dimensioni e per le numerose features:

[checklist]

  • supporta i principali procotolli dello stack: IP, ICMP, UDP, TCP, IGMP, ARP, PPPoS, PPPoE
  • include diversi clients: DHCP, DNS, SNMP…
  • offre diversi livelli di astrazione: raw API, sequential API e BSD-style API

[/checklist]

 

lwip

In rete sono disponibili diverse risorse (tutorial, manuali…) relativi all’utilizzo di tale libreria; un buon documento introduttivo, sebbene non riferito al chip ESP32, è quello di Atmel: AT04055 – Using the lwIP Network Stack.

La libreria lwIP si trova all’interno della cartella components/lwip del framework. Il collegamento tra le funzioni core del framework e la libreria lwIP è contenuto nel codice tcpip_adapter:

lwip-01

HTTP client

In questo tutorial vedremo un primo esempio, molto semplice, di comunicazione TCP: l’implementazione di un client HTTP. Il protocollo HTTP (Hypertext Transfer Protocol) è il “linguaggio” parlato dai siti web; il client HTTP che andremo a sviluppare si comporterà quindi come un browser:

  • apre una connessione verso un server che ospita un sito web
  • invia la richiesta per una risorsa (pagina HTML, immagine…)
  • riceve la risposta e la stampa a video (via seriale)

lwip-02

Vediamo le fasi nel dettaglio; il sorgente completo dell’esempio è disponibile nel mio repository su Github.

Connessione TCP

Nella prima fase, il client effettua una connessione, utilizzando il protocollo TCP, al server. Un server può accettare diverse connessioni in ingresso: per distinguere i vari servizi che il server offre, il protocollo TCP utilizza il concetto di porta, un identificativo numerico  che va da 0 a 65535. Alcuni servizi utilizzano porte standard: i siti web sono normalmente “esposti” sulla porta 80 (o 443 se utilizzano la versione cifrata del protocollo, HTTPS).

Applicativamente la connessione verso un server viene rappresentata da un socket: possiamo immaginare il socket come un “tubo” attraverso il quale transitano i dati che scambiamo con il server al quale siamo collegati.

Dopo aver configurato l’interfaccia di rete come abbiamo imparato nei precedenti tutorial (in particolare è il comando tcpip_adapter_init() a inizializzare la libreria lwIP) dobbiamo quindi creare un nuovo socket:

const struct addrinfo hints = {
  .ai_family = AF_INET,
  .ai_socktype = SOCK_STREAM,
};
int s = socket(res->ai_family, res->ai_socktype, 0);

Nel nostro caso il socket s utilizzerà il protocollo TCP (SOCK_STREAM) della “famiglia” di protocolli IP (AF_INET). I protocolli che lwIP supporta sono definiti nel file sockets.h:

lwip-03

Il terzo parametro (0 nel nostro caso), viene utilizzato per specificare l’ID del protocollo solo in caso di SOCK_RAW.

Se la chiamata va a buon fine, la variabile s rappresenta l’identificativo numerico del nuovo socket creato. In caso di errore invece viene restituito -1.

Una volta ottenuto un socket, è possibile effettuare la connessione al server remoto con la funzione:

connect(int socket, const struct sockaddr *address, socklen_t size)

che ha come parametri l’identificativo del socket da utilizzare, la struttura (sockaddr) che contiene le informazioni sull’indirizzo del server remoto e la dimensione di tale struttura.

Normalmente si conosce il nome del server a cui ci si vuole collegare, non direttamente il suo indirizzo IP. La conversione tra nome e indirizzo IP avviene tramite il servizio DNS (Domain Name System). Possiamo utilizzare la funzione getaddrinfo per interrogare i server DNS:

struct addrinfo *res;
int result = getaddrinfo(CONFIG_WEBSITE, "80", &hints, &res);

Se la risoluzione DNS ha esito positivo, la struttura res contiene tutte le informazioni necessarie per effettuare la connessione:

result = connect(s, res->ai_addr, res->ai_addrlen);

Richiesta HTTP

Una volta aperta la connessione, possiamo inviare al server la richiesta per la risorsa che vogliamo ottenere. Una semplice richiesta HTTP ha la seguente forma:

GET risorsa HTTP/1.1
Host: sitoweb

La richiesta utilizza il metodo GET, specifica il percorso della risorsa, la versione del protocollo e il nome del sito web a cui la risorsa appartiene (necessario se il server ospita più siti web). Una cosa molto importante da sapere è che ogni richiesta termina con una riga vuota.

Ad esempio utilizziamo una pagina sul mio sito che in maniera randomica restituisce un aforisma (ho già usato questa pagina in un tutorial relativo al chip enc28j60):

lwip-04Ho sottolineato il nome del sito e cerchiato il percorso della risorsa. La richiesta da inviare al server per ottenere tale risorsa sarà quindi:

GET /demo/aphorisms.php HTTP/1.1
Host: www.lucadentella.it

Nell’esempio che trovate su Github ho parametrizzato (via menuconfig) sia il sito web che la risorsa, quindi la costante che contiene la richiesta da inviare ha la forma:

// HTTP request
static const char *REQUEST = "GET "CONFIG_RESOURCE" HTTP/1.1\n"
  "Host: "CONFIG_WEBSITE"\n"
  "User-Agent: ESP32\n"
  "\n";

Ho aggiunto un ulteriore parametro, non obbligatorio: lo User-Agent. Tramite questo parametro è possibile indicare al server di destinazione quale applicativo sta inviando la richiesta (nome del browser, sua versione…). Questa informazione è utilizzata sia a fini statistici, sia per adattare il sito web alle caratteristiche/capacità del programma che lo visualizzerà.

L’invio della richiesta al server avviene con la funzione write:

result = write(s, REQUEST, strlen(REQUEST));

Risposta

E’ possibile ricevere dati da un socket tramite la funzione read():

char recv_buf[100];
[...]
do {
  bzero(recv_buf, sizeof(recv_buf));
  r = read(s, recv_buf, sizeof(recv_buf) - 1);
  for(int i = 0; i < r; i++) {
     putchar(recv_buf[i]);
  } 
} while(r > 0);

La funzione read riceve – se disponibili – dei dati dal socket, li memorizza nel buffer indicato come parametro e restituisce il numero di bytes letti.

Demo

Ecco uno screenshot dell’esempio in funzione:

esp-tcp01

Related Posts

13 comments

lucadentella.it – ESP32 (13) – Inviare SMS 27 marzo 2017 - 08:31

[…] è disponibile nel mio repository Github. La sua struttura base è molto simile a quella di un precedente tutorial: anche in questo caso infatti implementeremo un client HTTP che invierà la richiesta al server di […]

Reply
Omar Morando 9 maggio 2017 - 08:17

Ciao Luca,

innanzitutto complimenti per l’ottimo blog, chiaro ed esaustivo.

Ti chiedo un aiuto, è da poco che “gioco” con la ESP32. Devo realizzare una connessione peer-to-peer tra due ESP32 usando wifi o bluetooth, il requisito che è si scambino dei semplici comandi (es. “comando1”, “comando2” ecc.) con un tempo di attivazione molto ridotto, poche decine di millisecondi, da quando quella denominata master riceve un input da GPIO.

Qual è la soluzione migliore? Hai un esempio di aiuto?

Grazie

Reply
luca 9 maggio 2017 - 16:21

Ciao Omar e grazie per i complimenti, fanno sempre piacere 😉 Per la tua applicazione vanno bene entrambi i protocolli… penso che l’uso del wifi sia più semplice (niente pairing…). Sto preparando proprio un tutorial sull’uso delle netconn API di lwip per connessioni socket e un altro sulla modalità Access Point di ESP32 che ti saranno sicuramente utili 😉

Reply
Omar Morando 11 maggio 2017 - 08:37

Ottima notizia! Spero di leggerlo presto allora.
Lo pubblicherai sul blog e su Github?
Grazie

Reply
luca 11 maggio 2017 - 09:30

sicuramente 😉 nelle prossime settimane

Reply
lucadentella.it – ESP32 (17) – SNTP 11 maggio 2017 - 08:57

[…] un precedente articolo vi ho parlato della libreria lwip, scelta dal framework esp-idf per gestire le comunicazioni di […]

Reply
Shantih Etzi 17 agosto 2017 - 10:40

Ciao Luca,
Adoro i tuoi tutorial!!

Solo ho qualche piccolo problemino con questo programma…
Ho provato con google e con il tuo sito demo degli aforismi, ma continua a darmi “Unable to connect to the target website”
Inoltre ho dovuto disattivare il brownout sensor perché continuava a resettarsi, ma immagino questo sia un problema della scheda xD

Reply
Marius 14 novembre 2017 - 22:04

Thanks Luca for your tutors,

got request errors till I added some \r

static const char *REQUEST = “GET “WEB_RESOURCE” HTTP/1.1\r\n”
“Host: “WEB_SITE”\r\n”
“User-Agent: ESP32\r\n”
“\r\n”;

Reply
luca 15 novembre 2017 - 10:15

Thanks Marius, you’re right: HTTP specs require \r\n but some servers also accept \n.

Reply
jserena 15 aprile 2018 - 10:59

Thanks for your tutorials, I hope you will continue writing them for a long time.

The first flashing of this tutorial gave me a panic error. Looking in tutorial 02 I noticed that there were a NVS initialization. That solved the panic error.

Also I got Bad Request until I changed \n for \r\n.

Reply
briand 25 maggio 2018 - 01:38

ciao Luca,
Grazie per i tuoi tutorial!! sono un beginer con l hardware. vorrei stabilire una connessione wifi tra il mio pc e esp32 per trasferire dati ( wifi scanner ) raccolti dal esp32 collegando il mio esp32 direttamente sulla presa ( non sul pc). ho provato di usare il tutorial 8 ma mi sono perso.
grazie in anticipo per una soluzione.

Reply
luca 3 giugno 2018 - 20:11

Ciao Briand… prova a vedere se il tutorial su mqtt fa al caso tuo!

Reply
Chirag Agrawal 5 aprile 2020 - 15:03

I had to add “ESP_ERROR_CHECK(nvs_flash_init());”
to make it work. Thanks for the amazing tutorial !

Reply

Leave a Comment

10 + 10 =