[TCP]ritardo loop connessione wifi (programmare ESP8266)

Ciao a tutti amici!

Sto avendo un problemino e sono 2 giorni che provo a risolvere ma neanche in rete ho trovato una soluzione e per questo mi affido a voi.
Sto realizzando un progetto dove il mio Arduino Mega invia dei dati tramite ESP8266 (quindi wifi) ad un web server per memorizzarli. La connessione che uso è TCP. Purtroppo non sono un grande esperto di protocolli internet e quello che ho realizzato finora lo devo a ricerche internet.

Veniamo al problema:
Tutto funziona bene e riesco a registrare dati anche una volta al secondo (che credo sia un buon risultato o comunque sia per il mio scopo più che sufficiente (da tener presente che nel loop non ci sono delay())
Il problema viene fuori quando la connessione è un po' lenta; infatti può arrivare ad impiegare anche 20 secondi per la registrazione di ogni GET mandata al server ed in questo lasso di tempo Arduino non continua ad eseguire il loop ma mentre è in comunicazione col server non esegue nient'altro (me ne sono accorto perché ho un LCD touch collegato al MEGA e quando la connessione è lenta se premo un pulsante sul touch prima che Arduino reagisca passano quei secondi in cui si sta eseguendo l'invio della GET e la ricezione della risposta e questo è un bel problema!!!).
Quale potrebbe essere una valida soluzione secondo voi?
Facendo ricerche ho letto che la connessione UDP è molto più veloce della TCP ma ha di contro che non esegue il controllo che i dati siano stati ricevuti. Però prima di avventurarmi a cambiare tipo di connessione vorrei chiedere un vostro parere; considerate che devo memorizzare dei dati e qualora si perdano alcuni pacchetti non è un enorme problema ma se la connessione è effettivamente lenta ho paura che vengano persi troppi dati o addirittura tutti rispetto alla TCP o forse sbaglio? ::slight_smile:
Voi cosa ne pensate? Potrebbe risolvere il mio problema o peggiorarlo?
La libreria che uso per l'ESP8266 è la seguente: ITEADLIB_Arduino_WeeESP8266-master
Facendo un debug con connessione lenta ho notato che praticamente tutte le funzioni della libreria che uso rallentano il loop. Come vedete dallo sketch queste funzioni sono:
wifi.createTCP
wifi.send
wifi.recv
wifi.releaseTCP
dove all'interno della libreria il wifi. viene sostituito con ESP8266.

Secondo me le soluzioni possono essere almeno 2:

  1. cercare di modificare la libreria o addirittura sostituirla se ne conoscete di migliori facendo in modo che le istruzioni vengano eseguite in una sorte di multitasking rispetto al loop ma non saprei assolutamente da dove cominciare! :o

  2. cambiare il tipo di connessione da TCP ad UDP.

Se avete altre soluzioni siete i benvenuti! O se anche secondo voi una di queste può essere una buona soluzione ne possiamo discutere insieme.

Grazie a tutti!

Se può essere di aiuto questo è lo sketch che uso :slight_smile: :

#include <Nextion.h>
#include <SPI.h>
#include <GravityRtc.h>
#include <Wire.h>
#include <SD.h>
#include "ESP8266.h" 
#define SSID        "" //tuo SSID
#define PASSWORD    "" //tua PASSWORD
#define HOST_NAME   "" //server 
#define HOST_PORT   (80)

ESP8266 wifi(Serial1);
#define MAXLENGHT 150
char buf[7];

int ph =-1;
int ground1=-1;
int ground2=-1;
int ground3=-1;
int anidrideC=-1;
int decibel=-1;
float aci=-1;
int onda=-1;
int light=-1;
int acqua=-1;
int Co2pin=A15;
int ppm=0; 
char pHstring[4];
int frequenza=0;
int intensita=0;
boolean dataLoggerRequest= 0;
boolean boolonoff1=1;
boolean boolonoff2=1;
boolean boolonoff3=1;
boolean boolMode1=1;
boolean boolMode2=1;
boolean boolMode3=1;
int pagina=1;
int minima1 = 40;
int minima2 = 40;
int minima3 = 40;
int massima1 = 45;
int massima2 = 45;
int massima3 = 45;
int lux=0;
int serbatoio=0;
File logfile;
int pHArrayIndex=0;
const int chipSelect = 10;
char filename[] = "LOGGER00.txt";
GravityRtc rtc;

void invioDati(void)
{
  
char GET[MAXLENGHT] = "GET /blabla/write.php?g1=";
char GET2[]="&g2=";
char GET3[]="&g3=";
char GET4[]="&c1=";
char GET5[]="&p1=";
char GET6[]="&l1=";
char GET7[]="&d1=";
char GET8[]="&m1=";
char GET9[]="&w1=";
char GETfine[]=" HTTP/1.1\r\nHost: indirizzo_server_qui\r\n\r\n"; //Connection: close\r\n\r\n
  Serial.println("PRIMA DI CREAZIONE TCP_________*******");
  if (wifi.createTCP(HOST_NAME, HOST_PORT))
   {
   Serial.print("create tcp ok");
   }
   else 
   {
   Serial.print("create tcp err");
   }
   Serial.println("DOPO DI CREAZIONE TCP_________*******");
  char g1[4];
  char g2[4];
  char g3[4];
  char c[7];
  char p[5];
  char a[3];
  char db[7];
  char w[7];
  char l[7];
  strcpy(g1,itoa(ground1, buf, 10));
  strcpy(g2,itoa(ground2, buf, 10));
  strcpy(g3,itoa(ground3, buf, 10));
  strcpy(c,itoa(anidrideC, buf, 10));
  strcpy(p,itoa(ph, buf, 10));
  strcpy(a,itoa(acqua, buf, 10));
  strcpy(db,itoa(decibel, buf, 10));
  strcpy(w,itoa(onda, buf, 10));
  strcpy(l,itoa(light, buf, 10));
  strcat(GET,g1);
  strcat(GET,GET2);
  strcat(GET,g2);
  strcat(GET,GET3);
  strcat(GET,g3);
  strcat(GET,GET4);
  strcat(GET,c);
  strcat(GET,GET5);
  strcat(GET,p);
  strcat(GET,GET6);
  strcat(GET,a);
  strcat(GET,GET7);
  strcat(GET,db);
  strcat(GET,GET8);
  strcat(GET,w);
  strcat(GET,GET9);
  strcat(GET,l);
  strcat(GET,GETfine);
  Serial.print("stringa mandata: ");
  Serial.print(GET);

     Serial.println("PRIMA DI WIFI.SEND_________*******");

  wifi.send((const uint8_t*)GET, strlen(GET));

  Serial.println("DOOPO DI WIFI.SEND_________*******");
  
  uint8_t buffer[1024] = {0};

  Serial.println("PRIMA DI WIFI.RECV_________*******");
  
   uint32_t len = wifi.recv(buffer, sizeof(buffer), 10000);
   // per non disconnettere commentare da qui 

   Serial.println("DOPO DI WIFI.RECV_________*******");
   if (len > 0) 
   { 
   Serial.print("Received:[");
   for(uint32_t i = 0; i < len; i++)
   {
   Serial.print((char)buffer[i]);
   }
   Serial.print("]");
   }
   //oppure provare da qui per non togliere debug 

   Serial.println("PRIMA DI WIFI.RELEASE_TCP_________*******");
   if (wifi.releaseTCP())
   {
   Serial.print("release tcp ok");
   }
   else
   {
   Serial.print("release tcp err");
   }
   Serial.println("DOPO DI WIFI.RELEASE_TCP_________*******");
   //a qui 
}

const int sensor_1 = 6;


void setup()
{

  //**************SETUP***************
  //*****************************
  Serial.begin(9600);
  nexInit();
  Serial.println("solo nel setupppppppppppppppppp");

  pinMode(22, INPUT);
  pinMode(chipSelect, OUTPUT); // SD card pin select
  Serial.print("FW Version:");
  Serial.println(wifi.getVersion().c_str());
  if (wifi.setOprToStationSoftAP())
  {
    Serial.print("to station + softap ok\r\n");
  }
  else
  {
    Serial.print("to station + softap err\r\n");
  }
  if (wifi.joinAP(SSID, PASSWORD))
  {
    Serial.print("Join AP success\r\n");
    Serial.print("IP:");
    Serial.println( wifi.getLocalIP().c_str());
  }
  else
  {
    Serial.print("Join AP failure\r\n");
  }
  if (wifi.disableMUX()) 
  {
    Serial.print("single ok\r\n");
  }
  else
  {
    Serial.print("single err\r\n");
  }
  delay(200);
}

void loop()
{
  intensita =analogRead(10);
  lux=map(analogRead(9),0,1032,1,6000);
  //Invio dati al DATALOGGER
  dataLoggerRequest=1;
Serial.println("PRIMA DI INVIO_DATI_________*******");

  if (dataLoggerRequest)
  {
    invioDati();
  }
  Serial.println("DOPO DI INVIO_DATI_________*******");
}

Premetto che non conosco quella libreria ... ma la prima cosa che viene in mente, e' che possa essere bloccante, cioe' che al suo interno ci sia qualcosa che, finche' non riceve quello che si aspetta, non restituisce il controllo al loop ...

  1. se passi a UDP secondo me dovrai modificare il sever web per mandare una conferma di dati ricevuti per far sapere ad Arduino se i dati arrivano.

  2. ma hai molte serial.print() dal Serial monitor cosa vedi quando la connessione è lenta ? Dove sembra sia "fermo" il codice di Arduino ?

Ho inserito alcuni dei Serial.print proprio per capire cosa blocca il loop e quando la connessione è lenta si blocca per qualche secondo "random" (tra circa 3 e 8 secondi) praticamente ogni volta che il modulo wifi si connette al server, ovvero quando si crea la TCP, quando si inviano i dati, quando li si ricevono e quando si chiude la TCP.
Se non avete modo di scaricare la libreria vi posso scrivere la parte di codice relativa a queste funzioni se volete...

nid69ita:

  1. se passi a UDP secondo me dovrai modificare il sever web per mandare una conferma di dati ricevuti per far sapere ad Arduino se i dati arrivano.

Ma se parla di "server Web" e di "GET" si tratta di un server http (quindi Apache, IIS..) che è un protocollo TCP, quindi niente UDP. Se il server deve acquisire pacchetti UDP lo può fare solo implementando un servizio ad hoc che stia in ascolto. Il tutto è ovviamente più complicato del normale GET ma comunque provo a descriverti la cosa, poi vedi tu se può darti qualche spunto.

Che l'UDP sia connectionless è vero, ma si può sempre implementare un protocollino (provo ora a inventarlo) per cui il sistema A manda a B un pacchetto UDP col dato, e possibilmente con un identificativo univoco, anche solo un progressivo, e quando B lo riceve risponde con un altro pacchetto verso B con un "ACK" seguito dall'identificativo. Tipo (dove "001" è l'identificativo):

A UDP(B)"001DATO" ---- > B

A <----- UDP(A)"001ACK" B

A quel punto A può mandare anche 5 o 6 pacchetti, purché ne mantenga traccia in un qualche stack, dal quale rimuove i pacchetti dei quali ha ricevuto ACK. Memorizzando anche l'ora (o millis) di invio, è possibile di tanto in tanto verificare se qualche pacchetto non è stato ricevuto (ossia è ancora nello stack) e fare qualcosa, come inviare nuovamente il pacchetto, aggiornando il tempo di invio.
Una cosa del genere.

Tutto questo però implementando questa comunicazione anche sul server, per cui la soluzione migliore sarebbe ovviamente fare il GET ma serializzando le richieste (se devi aspettare la risposta) quindi vedendo come non far bloccare tutto lo sketch (Arduino non è multithread...)..

E quindi stai reinventando una versione da debuggare e testare di TCP che usa UDP? Un sacco di volte c'è la tentazione di fare ciò, ma non ha senso. Si usa UDP se e solo se perdere un pacchetto è accettabile e indolore, altrimenti su usa TCP, fine. Tutto il resto è cercarsi problemi, IMHO.

Il problema riscontrato, comunque, è normale. La connessione è sincrona e "in diretta", non viene effettuata in background, per cui c'è poco da fare, bisogna accettare il tempo che richiede. Non sei su una scheda con un sistema operativo che riceve i pacchetti dalle applicazioni, li bufferizza, accoda, invia, fa altrettanto con le risposte, ecc... Forse, e dico forse (non ho mai studiato i dettagli interni di funzionamento dell'ESP) l'hardware permetterebbe anche di farlo in background, ma non mi risulta che alcuna libreria attualmente esistente permetta questa possibilità.

Se sei disposto a perdere potenzialmente pacchetti, usare UDP sicuramente richiede meno tempo per l'invio ma, come dice docdoc, dovrai avere un servizio ad-hoc per la ricezione dall'altro lato, non potrai più usare HTTP.

In ogni caso, la libreria che stai usando a me non piace molto, preferisco WiFiEsp.

SukkoPera:
E quindi stai reinventando una versione da debuggare e testare di TCP che usa UDP?

Si, non è divertente? :slight_smile: Di protocolli (anche se in genere TCP ovviamente....) per lavoro mi sono sempre divertito a crearne, che ci trovi di male? E daje sukko, fattela 'na risata! :smiley:

Un sacco di volte c'è la tentazione di fare ciò, ma non ha senso. Si usa UDP se e solo se perdere un pacchetto è accettabile e indolore, altrimenti su usa TCP, fine. Tutto il resto è cercarsi problemi, IMHO.

Si, certo, ma in QUESTO caso se non hai un sistema multithread mi spieghi come gestisci tante richieste asincrone senza bloccare il processo del client? Visto che la libreria sembra fermarsi in attesa della risposta, la mia era solo una soluzione "non canonica" ad un problema che aveva esposto l'amico, ma che, seppure non perfetta/elegante, potrebbe risolvergli il problema non ti pare? :wink:

No, non mi pare. Se, come credo, hai studiato le reti ed i loro protocolli, sai benissimo quanti potenziali problemi ci sono da gestire (pacchetti persi, duplicati, ricevuti in ordine diverso da quello di invio, gli stessi problemi per gli ack, e quant'altro), e gestirli adeguatamente richiede la complessità e pesantezza di TCP. Per avere una soluzione approssimativa, tanto vale non averla, almeno secondo me, e rassegnarsi che qualche pacchetto vada perso.

Ho visto meglio il codice proposto dall'OP e, nel caso specifico, credo che la prima cosa da fare per velocizzare il tutto sia quella di evitare di aprire e chiudere la connessione per ogni singolo invio, come mi pare faccia. La aprirei all'inizio e la terrei aperta, in modo da dover fare solo la singola send() per ogni invio.

Eventualmente, si potrebbe anche vedere se si può disabilitare l'algoritmo di Nagle (TCP_NODELAY), ma non credo che intervenga in questo caso (I dati mi sembrano abbastanza corposi, e non so nemmeno se l'ESP lo implementi).

SukkoPera:
nel caso specifico, credo che la prima cosa da fare per velocizzare il tutto sia quella di evitare di aprire e chiudere la connessione per ogni singolo invio, come mi pare faccia. La aprirei all'inizio e la terrei aperta, in modo da dover fare solo la singola send() per ogni invio.

Quindi (sempre se il server accetta request multiple nella stessa sessione) una send senza verificare la risposta del server? Equivale a mandare un pacchetto UDP a quel punto..

Insomma, il problema come ho detto non è se TCP o UDP, è che se il problema è legato al fatto che il server potrebbe metterci vari secondi a dare conferma, Arduino, che non è multithreaded, deve stare ad aspettare.
Per cui la mia soluzione era quella di disaccoppiare la richeista dalla risposta, e gestire in uno stack le richieste per le quali non ha ancora ricevuto risposta, gestendo eventualmente i retry.

Per me, se ho compreso bene i requisiti del nostro amico, può funzionare, mentre con le soluzioni che di ci tu, no, anzi dici di rinunciare.
Beh, come si dice, le opinioni sono come le palle, ognuno ha le sue.. :wink:

E comunque se sei d'accordo, per me meglio lasciare spazio al problema specifico del nostro amico, inutile intasare il thread con discorsi sulla filosofia dei protocollo di rete. :smiley:

No, dico di fare solo la send, verificando il risultato in maniera magari asincrona (qua dipende da cosa permettono le librerie). Questo perché l'apertura della connessione è la parte più lenta, tra risoluzione DNS, handshake e quant'altro.

Per il resto i discorsi sul protocollo mi sembrano esattamente il centro della discussione, perché credi che intasino il thread? :wink:

Grazie a tutti dell'aiuto che mi state dando.

Premetto che nei vostri discorsi ogni tanto mi sono perso dato che non sono un esperto ma sono arrivato alla conclusione per prima cosa bisogna provare a cambiare la libreria come consigliato da @SukkoPera e se cambia qualcosa a parità di connessione e sketch.

Poi voglio avvisarvi che avevo già provato ad eliminare la parte dello sketch che si occupava della ricezione della risposta dal server che mi prendeva circa il 20% del ritardo da me lamentato senza compromettere nient'altro.

Ho anche provato come da voi consigliato ad aprire la connessione una volta senza chiuderla e mandare in sequenza le GET, questo funziona e va ancora più veloce però alla prima "caduta della connessione TCP" (perchè dopo qualche minuto cade) si blocca l'invio dei dati ovviamente.
Per risolvere credo si possa sfruttare una qualche funzione della libreria che verifica se la connessione c'è e ricreare la TCP qualora sia caduta con la speranza che la verifica con occupi un'eternità come la creazione stessa della TCP.

Poi per quanto riguarda l'UDP mi avete un pochino scoraggiato e quindi la lascerei da parte per adesso.

Quindi come detto farò queste prove nei giorni successivi ma se intanto avete qualcosa da aggiungere o da correggermi siete come sempre i benvenuti!
grazie

[WiFiEsp] Warning: Unsupported firmware

SukkoPera:
In ogni caso, la libreria che stai usando a me non piace molto, preferisco WiFiEsp.

Come consigliato da @SukkoPera ho provato a cambiare libreria ma qualsiasi esempio carico mi ritrovo con il seguente errore:
[WiFiEsp] Warning: Unsupported firmware

Infatti mi sono ricordato di aver già provato questa libreria e mi dava lo stesso errore.
Mentre con l'altra libreria ottengo ciò:
"FW Version:00200.9.4"

Questa è la mia schedina: https://www.dfrobot.com/product-1279.html
Come posso risolvere secondo voi?

Se funziona con l'altra libreria, probabilmente puoi semplicemente ignorare quel messaggio e andare avanti.

Ciao a tutti di nuovo.
Ho lasciato un po' "appeso" il problema che avevo quando ho scritto questo post perché mi sono reso conto che la strada da percorrere non era semplicissima e credevo che quel problema non fosse così importante ma non è così, mi sono infatti accorto di doverlo risolvere a tutti i costi.

Mi sono quindi messo a pensare a come risolvere e ho avuto 2 idee, una più semplice ed immediata anche se sicuramente più dispendiosa mentre l'altra un po' più complessa (sicuramente per me) e forse neanche fattibile. Per questo chiedo di nuovo aiuto a voi...

Prima soluzione:
Ho pensato di poter collegare un Arduino "economico", come ad esempio Arduino Uno, al mio Arduino Mega e lasciare che sia l'Uno ad effettuare la connessione e l'invio dati wifi. Infatti sarà il Mega a fare tutto il lavoro del mio progetto ma invece che effettuare lui la connessione wifi (che come ricorderete mi dava problemi di ritardi nell'esecuzione del loop) invierà semplicemente quei dati via Seriale all'Arduino Uno e data la semplicità e rapidità della Seriale sicuramente non avrà i ritardi della connessione TCP. Poi connetterò la solita scheda wifi basata su esp8266 (ESP8266 Wifi Bee (Arduino Compatible) - DFRobot) sull'Uno e procederò in tutta tranquillità all'invio dei dati in wifi.

Seconda soluzione:
Ho visto molte persone in rete che riescono a programmare l'ESP8266 con programmi ad hoc per le proprie esigenze e mi è venuto il dubbio che questo potesse venire a mio favore.
Quindi sostanzialmente ciò che vi chiedo in questo nuovo post è:

1)Posso programmare la mia ESP8266 in modo tale che riceva i dati dal Mega in seriale e si connetta al server tramite un processo interno alla scheda che non convolga l'Arduino Mega? Perché se la scheda si può programmare credo abbia un processore e qualche tipo di memoria all'interno...

Se la mia scheda specifica non va bene per quanto detto sopra, esiste un'altra scheda che possa far ciò?

Grazie ancora ragazzi

Sì, puoi fare come dici, programmando direttamente l'ESP per bufferizzare e "scaricare" i dati via wifi in maniera asincrona.

Fantastico... mi documenterò allora!!!

Salve di nuovo!

Vorrei aggiornarvi sui miei progressi riguardo il mio progetto.

Allora, come promesso mi sono documentato per capire come programmare la schedina esp8266 e credo di essere riuscito ad intuire come farlo tramite l'IDE di Arduino. Ci sono molti tutorial su come programmarla per fare le GET request al server senza l'uso di Arduino ed in parte è quello che mi serve però non ho trovato molto per quanto riguarda la comunicazione tra Arduino e l'ESP8266. Infatti non saprei come fare per memorizzare la stringa nella schedina ma considerando che sono in grado di far ciò tra 2 Arduino e considerando che l'esp8266 si può programmare nella stessa maniera e linguaggio di Arduino ho creduto che basta programmarla proprio come potrei fare con un qualsiasi Arduino almeno per quanto riguarda la memorizzazione della stringa...
Però prima di passare ai fatti e fare dei tentativi vi chiedevo se il mio ragionamento è giusto o se sono fuori strada...

Una buona soluzione è quella di usare un ring buffer.

Cosa sarebbe un ring buffer? :frowning: :o
E poi dici di usarlo nel programma per Arduino o per l'esp8266?