Errore nel file 'AppData\Local\Arduino15\packages\esp32\hardware\esp32\3.0.x\libraries\Network\src\NetworkClient.cpp'

Come da titolo, c'è un errore nel suddetto file. La correzione da fare è la seguente:

originario:
  if (_connected) {
    uint8_t dummy;
    int res = recv(fd(), &dummy, 0, MSG_DONTWAIT);
    // avoid unused var warning by gcc
    (void)res;

modificato:
  if (_connected) {
    uint8_t dummy;
    errno = 0; // ********* aggiunto ***********
    int res = recv(fd(), &dummy, 0, MSG_DONTWAIT);
    // avoid unused var warning by gcc
    (void)res;
1 Like

Sarebbe interessante sapere perchè seve questa tua correzione

Sono curioso anche io a dire il vero.
Tra l'altro sul core v3.0.7 io compilo senza problemi.

Il metodo completo della classe è questo e la variabile errno dovrebbe essere impostata dalla funzione recv(), una macro facente parte dello stack TCP/IP dell'ESP32.
La variabile infatti non è un membro di NetworkClient.

uint8_t NetworkClient::connected() {
  if (fd() == -1 && _connected) {
    stop();
  }
  if (_connected) {
    uint8_t dummy;
    int res = recv(fd(), &dummy, 0, MSG_DONTWAIT);
    // avoid unused var warning by gcc
    (void)res;
    // recv only sets errno if res is <= 0
    if (res <= 0) {
      switch (errno) {
        case EWOULDBLOCK:
        case ENOENT:  //caused by vfs
          _connected = true;
          break;
        case ENOTCONN:
        case EPIPE:
        case ECONNRESET:
        case ECONNREFUSED:
        case ECONNABORTED:
          _connected = false;
          log_d("Disconnected: RES: %d, ERR: %d", res, errno);
          break;
        default:
          log_i("Unexpected: RES: %d, ERR: %d", res, errno);
          _connected = true;
          break;
      }
    } else {
      _connected = true;
    }
  }
  return _connected;
}

Avevo(ho) implementato un webserver per domotica. Non riuscivo a caricare tutti gli oggetti, sul browser avevo l'errore di reset connessione. Ho messo l'esp32 in debug mode e ho visto che mi dava "errore" di connessione remota chiusa. Secondo me accade questo: in modalità sincrona la recv() torna: > 0 i byte letti, < 0 errore, == 0 connessione remota chiusa e setta errno di conseguenza. In modalità asincrona (MSG_DONTWAIT) torna esattamente come la sincrona per i primi due casi ed errno viene impostato correttamente. Nel terzo caso, un ritorno di zero può essere semplicemente che la momento della chiamata non ci sono dati disponibili. Non è un successo, non è un errore e probabilmente lascia la errno immodificata. Se la chiamata precedente aveva segnalato la chiusura remota, errno resta in quello stato. La chiamata corrente torna zero perché non ci sono ancora dati e l'errno fa scattare l'errore.
Spero di essere stato chiaro. Comunque con quell'aggiunta non ho più avuto problemi (tranne che ad ogni aggiornamento devo verificare se il sorgente è tornato allo stato iniziale)

Saluti, Nazzareno

1 Like

Interessante, in effetti ho avuto problemi simili anche io quando i file da servire erano molti, ma ho risolto diversamente.

Dovrei verificare se anche in questo modo ottengo lo stesso risultato (ammesso che la causa sia la stessa).
Che libreria stai usando per il web server? Quella inclusa nel core o la solita ESPAsyncWebServer?

Se la soluzione è collaudata, perché non provi a fare una pull-request nel repository github ESP32?

In che modo hai risolto?
Sto usando quella inclusa nel core.
Mi "vergognavo" un po' a riloggarmi in github dopo più di tre anni :flushed: oltretutto con il contenuto ormai "obsoleto", comunque, ok, ho inviato la segnalazione ... resto in attesa ...

In realtà mi ricordavo male, i problemi li ho avuti con la libreria ESPAsyncWebServer perché il task adibito a gestire il socket viene avviato con un quantitativo di memoria sufficiente solo per 3/4 connessioni concorrenziali.

Con la libreria inclusa nel core invece avevo riscontrato un'eccessiva latenza nel gestire le richieste del client (rispetto ad ESPAsyncWebServer).

La mia soluzione fu quella di ereditare la classe WebServer e fare l'overload del metodo handleClient().

Ho visto che nelle ultime release hanno eliminato l'inutile delay() che causava questa latenza, devo provare come va adesso.

Potresti adottare la stessa tecnica per affrancarti da eventuali aggiornamenti nel caso in cui non prendano in considerazione la tua pull request.

nella handleClient() c'è comunque la chiamata a connected() ... l'hai gestita diversamente?
L'aggiornamento del core non è poi così frequente, idem per il mio progetto, se dovessero verificarsi entrambe le cose ... me ne accorgo subito, non carica la pagina. Comunque è strano che non sia venuto alla luce o forse la libreria base è poco usata ...

Purtroppo qualsiasi tutorial online fa riferimento a ESPAsyncWebSer che è iper inflazionata.
Per quanto ottima, non è più mantenuta ormai da tempo.

La libreria inclusa nel core non ha nulla da invidiare, anzi per certi aspetti va anche meglio ed è più "integrata" nel core.

Io ho due librerie per gestire WebServer da FS dove eredito entrambi e dove uso quella inclusa nel core ho un consumo di risorse più contento.

... e ci fa piacere per lui! :joy:

Ciao, Ale.

Un tempo si diceva "colpa del T9"

:rofl::rofl:

2 Likes

Ciao, scusa, ho provato a vedere le tue lib, sono molto complesse (ovviamente devono ereditare e fare un bel pò di cose, pure il FS).
Non ho capito (curiosità) quale lib del core erediti. WebServer.h ?
Non sono esperto di webserver su esp32, stavo giusto provando in questi giorni con dei tutorial (questo: LINK); avevo inteso che le lib del core non potessero essere Async, quindi si ricorre alla famosa ESPAsyncWebSer.
Quindi al posto di quella si può usare tranquillamente la WebServer del core ?
Scusa, ma sono un pò confuso.

Guarda, di base la libreria ESPAsyncWebServer si chiama "Async" perché fa girare il webserver su un task dedicato che quindi non blocca il resto del codice. La differenza non è tutta qui ovviamente, ma questo è l'aspetto più importante secondo me.

Se ci fai caso infatti, in alcuni esempi nel loop() non c'è alcuna istruzione.
Con ESP32 viene usato un task FreeRTOS, con ESP8266 che non supporta FreeRTOS nell'implementazione Arduino, sinceramente non mi ricordo come l'autore aveva implementato la cosa. Bisognerebbe analizzare le due dipendenze AsyncTCP (ESP32) e ESPAsyncTCP (ESP8266).

Dal punto di vista funzionale non cambia praticamente nulla se non che i metodi sono leggermente diversi anche se concettualmente equivalenti.

Considera che lo sviluppatore del core ESP32 per Arduino attualmente più attivo è proprio l'utente Github me-no-dev ovvero l'autore della libreria ESPAsyncWebServer che da qualche tempo è stato assunto proprio da Espressif (il passaggio alla main release 3.x.x basato su ESP-IDF v5.1.x è quasi tutta opera sua).

1 Like

Ciao, ti rompo ancora,
sto provando la tua lib esp-fs-webserver, scaricata da ide 1.8.19, con un esp32 c3,
esempio SimpleServer.ino, mi da errore di compilazione esempio dentro a esp-fs-webserver.cpp, errore manca variabile TAG
riga 330
ESP_LOGI(TAG, "mdns hostname set to: [%s]", m_host.c_str());

Sbaglio qualcosa ?
Grazie, igor

No, errore mio che ho usato la macro senza definire correttamente TAG.

Per risolvere rapidamente imposta un livello di debug inferiore ad Info, come ad esempio Error oppure Warning.
In alternativa aggiungi al file esp-fs-webserver.cpp un
#define TAG "quellochevuoi"
oppure un
const char* TAG = "quellochevuoi";

1 Like

Se qualcuno volesse provare e confermare il problema, ho messo in https://www.ennepisoft.it/test_esp32/test_esp32.php il progetto di domotica 'completo' ripulito dalle chiamate ad hardware non presente. C'è il file 'readme.txt' che fornisce le informazioni necessarie.

Salve, nessuno? Nemmeno per pura curiosità? Per provarlo è sufficiente un esp32 e una schedina SD per i file del sito ... forse il problema è il link al sito? Uso questo sistema (metto i file in una cartella e poi ci pensa il codice php a farmi la lista e mettere i link) quando devo condividere dei file per non fare un unico zippone o mettere troppi link. Ok, in questo caso è un solo file, ma per abitudine ... Se volete metto il link direttamente allo zippato.
Comunque ripensando al problema penso di aver trovato il perché ed una soluzione 'più elegante'.
Rivedendo bene le specifiche della recv() errno viene impostato solo in caso di errore o se la connessione è stata chiusa da remoto. Quindi se non c'è errore e la connessione è in piedi torna il numero di byte che abbiamo richiesto senza impostare errno. Visto che abbiamo richiesto zero byte, è quello che torna, e visto che errno è una variabile globale, che può venire impostata in qualunque parte del programma, succede che viene elaborata anche quando res == 0 ma per nostra richiesta.
Metto qui il codice della connected. Se si scommenta '//#define RESET_ERRNO' si ha il workaround che avevo proposto precedentemente (a cui ho aggiunto la verifica che recv() torni un valore positivo, in questo caso mai). Altrimenti faccio fare la lettura di un byte ma lasciandolo nel buffer interno (usando MSG_PEEK)

uint8_t NetworkClient::connected() {
  if (fd() == -1 && _connected) {
    stop();
  }
  if (_connected) {
    uint8_t dummy;
//#define RESET_ERRNO
#ifdef RESET_ERRNO
    errno = ENOENT; // <= aggiunto
    int res = recv(fd(), &dummy, 0, MSG_DONTWAIT);
    if(res > 0)
      Serial.printf("recv return %d byte\n", res);
#else
    int res = recv(fd(), &dummy, 1, MSG_DONTWAIT | MSG_PEEK);
#endif
    // avoid unused var warning by gcc
    (void)res;
    // recv only sets errno if res is <= 0
    if (res <= 0) {
      switch (errno) {
        case EWOULDBLOCK:
        case ENOENT:  //caused by vfs
          _connected = true;
          break;
        case ENOTCONN:
        case EPIPE:
        case ECONNRESET:
        case ECONNREFUSED:
        case ECONNABORTED:
          _connected = false;
          log_d("Disconnected: RES: %d, ERR: %d", res, errno);
          break;
        default:
          log_i("Unexpected: RES: %d, ERR: %d", res, errno);
          _connected = true;
          break;
      }
    } else {
      _connected = true;
    }
  }
  return _connected;
}

Ho provato la tua libreria (ho commentato alcuni on(...) che erano in conflitto col mio codice) ed in effetti non da quel problema.
Ho visto che la differenza principale della handleClient() è la presenza, nel core del seguente pezzo di codice:

            if (_currentClient.isSSE()) {
              _currentStatus = HC_WAIT_CLOSE;
              _statusChange = millis();
              keepCurrentClient = true;
            }

l'ho commentato e non ha dato più quel problema, ma se c'è quel codice ha il suo motivo e penso che la soluzione proposta da me sia più valida.
Comunque, non accontentandomi, ho voluto provare la async, non la tua ma direttamente quella principale.
Questa proprio non sono riuscita a farla funzionare.
Se deve caricare pochi file o comunque di piccola dimensione è ok (es. la pagina di login), ma quando passa all'index si blocca nella write().
Anche se non è colpa di questo codice che si occupa del trasferimento, lo posto per completezza:

//---------------------------------------
char g_Buff2[1024 * 4];
#define chunk g_Buff2
#define sizeChunk (sizeof(chunk))
//---------------------------------------
template<typename T> void loadAndsendFile(AsyncWebServerRequest* request, T filename, int mime) 
{
  DEBUG_WEB(Serial.printf("FileToSend:%s, mime:%s\n", filename, getContentType(mime)))
  if (!openFile(filename)) {
    handle404(request);
    return;
    }
  int32_t _dim = myFile.size();
  AsyncClient* client = request->client();
  DEBUG_WEB(Serial.printf("Client %s, dim:%d\n", (client ? "Ok" : "Err"), _dim))
  sendHeader(client, mime, _dim, (eHTML == mime) ? no_cache : ok_cache);
  int32_t dim = _dim;
  int fail = 0;
  while(dim > 0 && fail < 5) {
    int len = min((unsigned int)dim, sizeChunk);
    len = myFile.read((uint8_t*)chunk, len);
    if (!len)
      break;
    bool inside = dim > sizeChunk;
    const char* t = (const char*)chunk;
    fail = 0;
    do {
      // non cambia nulla tra i due
//      int sent = client->write(t, len);
      int sent = client->write(t, len, inside ? ASYNC_WRITE_FLAG_MORE : 0);
      DEBUG_WEB(Serial.printf("Dim =%d, Remain=%d, Len=%d, Sent=%d\n", _dim, dim, len, sent))
      dim -= sent;
      if(sent == len)
        break;
      t += sent;
      len -= sent;
      vTaskDelay(10);
      if(!sent)
        ++fail;
      } while(fail < 5);
    }   
  DEBUG_WEB(if(dim) Serial.println(" Failed "); else Serial.println(" Done "))
  myFile.close();
}
//-------------------------------------------------------------------

se volete posto qui un paio di log, uno quando cerca di caricare l'index, l'altro mettendo direttamente il nome di un'immagine abbastanza corposa, ma sono un po' lunghetti. Intanto metto i link diretti (senza nemmeno zipparli):
log_site (7kB)
log_solo_file (4kB)

Qualcuno sa da cosa può dipendere?

Si avevo notato anche io quella differenza, ma la ragione per cui all'epoca feci l'overload di handleClient() era la presenza di un delay() che ne rallentava il funzionamento.

Evidentemente hanno fatto migliorie a tutta la classe introducendo anche questo pezzo di codice...
Dovrei rivedere anche il sorgente della mia libreria per adeguarlo alle novità introdotte, ma in questo periodo non ho molto tempo da dedicarci purtroppo :face_exhaling:

Ho finalmente provato la tua modifica ed effettivamente posso confermare che il problema delle connessioni "interrotte" quando ci sono file multipli non si presenta più.

Rimane il problema della latenza che talvolta è davvero eccessiva.