Ottimizzare Web Server (Arduino UNO + W5100)

Salve, ho un arduino uno + ethernet shield w5100 e sto sviluppando un web server che gestisce una scheda di 8 relè. Dal web server è possibile comandare manualmente i relè oppure con ritardo di spegnimento, in base al tempo impostato (00:00 = manuale).
Adesso, il sito funziona, ma non è il massimo della responsività, soprattutto nel caricamento iniziale che prende circa 10s.
Come potrei ottimizzarlo?
Vi lascio il codice in allegato

web_server.zip (6.72 KB)

Ho dato un occhiata al codice e non vedo tanti margini di miglioramento, potresti sfruttare le funzioni del C standard per resettare i vettori di char, per confrontare le stringhe ecc. ma miglioreresti praticamente nulla.
Il ritardo che evidenzi è la prima connessione, ovvero quando invii la pagina html che è "pesante" 32K quindi probabilmente è li che rallenta la risposta. Potresti provare a verificare (se già non lo hai fatto) con il monitor seriale se il maggior tempo perso è durante l'invio del file html.
Se è così come penso lavorando sulla pagina html per alleggerirla il più possibile magari riesci a rientrare in tempi accettabili.
Così su due piedi mi verrebbe da diti di ripensarla in modo da poter scrivere il codice javascript con for e parametri in modo da aver meno codice da inviare, unitamente a questo una volta riscritto il codice javascritp lo passerei a quelanche servizio di compressione del codice in modo da eliminare spazi, formattazioni, ecc. ogni byte risparmiato è un byte in meno da leggere ed inviare. Stessa cosa per la parte html, compatta, comprimi, elimina invii ecc.
Tieni la pagina formattata per lo sviluppo, poi quella in produzione la comprimi

Tornando al codice di Arduino, per le operazioni su EEPROM usa i metodi get e put, spazzi via tutto il codcie che hai fatto per leggere e scrivere i long, sicuramente è più ottimizzato

Sapresti farmi un esempio per quanto riguarda la scrittura du eeprom dei long? Ci ho provato con put e get ma non andava. Grazie

Forse usi un IDE o vecchio, perché le ultime incarnazioni hanno i metodi aggiornati e vanno, put e get scrivono e leggono qualsiasi formato di dato, contrariamente a read e write che leggono e scrivono un byte.
Se guardi nei link vedrai l'esempio del float, dell'array ecc. per i long non vi è differenza

antonello94:
Adesso, il sito funziona, ma non è il massimo della responsività, soprattutto nel caricamento iniziale che prende circa 10s.

Non mi è chiaro. Cosa intendi con "caricamento iniziale"?
Se parli del tempo tra l'accensione di Arduino e quando lo vedi disponibile ad un GET io nella funzione setup() non vedo nulla che possa arrivare addirittura a ben 10 secondi di ritardo.
Se invece parli del primo accesso dopo l'accensione di Arduino (dopo che si è inizializzato, quindi nel loop) intanto parli del normale GET o quello ajax?
In ogni caso secondo me ti conviene (è quello che farei io) mettere un po' di Serial.println() per capire quale fase/funzione interna alla risposta abbia tempi di risposta lunghi, credo tra queste:

SetOUTs(), readTime(), SetTimedOUTs(), EEPROMReadlong(), XML_response()

In ognuna metti come prima istruzione la print del nome funzione e millis iniziale, e alla fine il millis finale:

Serial.print("SetOUTs: START ");
Serial.println(millis());
...
Serial.print("SetOUTs: END ");
Serial.println(millis());

Detto questo, non ho capito bene lo scopo della funzione StrContains(): perché hai creato quella funzione invece di usare la strstr()? Non è questa la causa del rallentamento, ma mi pare inutile reinventare l'acqua calda... :wink:

Ossia invece di fare:

if (StrContains(HTTP_req, "ajax_inputs")) {

ti basterebbe fare:

if ( strstr(HTTP_req, "ajax_inputs") ) {

Per caricamento iniziale, intendo il tempo che ci mette la pagina web a comparire nel browser. Proverò a seguire i vostri consiglio e posto i risultati.

Ok, quindi è la pagina statica il problema e le chiamate Ajax invece vanno bene?

Comunque la index.html è da 32k byte, non dovrebbe metterci 10 secondi a leggere il file (cosa che potrai controllare eventualmente con i Serial.print che ti ho consigliato), ma per cercare di velocizzare potresti mandare la risposta tramite un buffer invece che carattere per carattere, una cosa del tipo:

...
byte buf[64]; // Buffer di output
...
          else {  // web page request
            // send rest of HTTP header
            client.println("Content-Type: text/html");
            client.println("Connection: keep-alive");
            client.println();

            // send web page
            webFile = SD.open("index.htm");        // open web page file
            if (webFile) {
              int c = 0;             
              while (webFile.available())
              {
                buf[c++] = webFile.read();
                if(c > 63)
                {
                  client.write(buf,64);
                  c = 0;
                }               
              }
              if(c > 0) client.write(buf,c);
              webFile.close();
            }
          }
...

Grazie mille docdoc!! Adesso il tempo di caricamento è di circa 2s. Un grandissimo miglioramento.

EDIT: Un eventuale miglioramento sarebbe usare dei cicli for nelle funzioni SetOUTs(), SetTimedOUTs() ecc... Visto che il codice si ripete. A tal proposito è possibile definire degli array che non risiedono nella ram? Ovviamente solo in lettura

Bene, sono contento! :slight_smile:

Ho lasciato un EDIT sopra, potresti dargli un occhiata?

antonello94:
Un eventuale miglioramento sarebbe usare dei cicli for nelle funzioni SetOUTs(), SetTimedOUTs() ecc... Visto che il codice si ripete.

Intanto ti consiglio di togliere di mezzo la Strcontains() e di sostituirla con strstr() che fa la stessa cosa ma più rapidamente.

Per quanto riguarda tutte quelle parti dove hai codice che si ripete, si, sarebbe possibile ma devi fare svariate modifiche, non banali se non hai mai fatto cose del genere.
Per cui se il programma fa ciò per cui lo hai pensato, lascialo com'è e ci penserai successivamente qualora dovessi fare qualche altra modifica sostanziale.
Se vorrai comunque farlo, devi in primo luogo trasformare in array i vari elementi e quindi gestirli tramite un indice.
Questo è solo un esempio di come "iniziare" quella conversione, poi cerca tu di capire cosa ho fatto, e quindi di adattare il resto del codice (se hai domande, chiedi pure!):

#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
#include <EEPROM.h>
// size of buffer used to capture HTTP requests
#define REQ_BUF_SZ   60

#define ON LOW
#define OFF HIGH

const byte OUT[8] = { A0, A1, 2, 3, 5, 6, 7, 8 }
const byte time[8] = { 0, 4, 8, 12, 16, 20, 24, 28 }
unsigned long startTime[8];
boolean timing[8];
...

void setup()
{
...
  // OUTs
  for(byte i=0; i<8; ++i)
 {
    pinMode(OUT[i], OUTPUT);
    digitalWrite(OUT[i], OFF);
  }
  Ethernet.begin(mac, ip);  // initialize Ethernet device
  server.begin();           // start to listen for clients
}
...
// checks if received HTTP request is switching on/off OUTs
// also saves the state of the OUTs
void SetOUTs(void)
{
  char cmd[16];
  for (byte i=0; i<8; ++i)
  { // Cerco il comando per questo OUT
    sprintf(cmd, "OUT%d=", i);
    char* p = strstr(HTTP_req, cmd);
    // Ho il comando per questo OUT?
    if ( p != 0 )
    { // Si, leggo il carattere dello stato e lo converto in bool
      // true=ON, false=OFF
      bool state = (p[5] == '1');
      // Imposto lo stato
      OUT_state[i] = state;
      digitalWrite(OUT[i], OUT_state[i]);
      if ( state == false ) // OFF
        timing[i] = false;
    }
  }
...

A tal proposito è possibile definire degli array che non risiedono nella ram? Ovviamente solo in lettura

Dipende. Si può fare qualcosa, ad esempio usando la EEPROM o la Flash, ma dipende da cosa ti serve e soprattutto SE e A COSA ti serve... :wink:

Alla fine ho deciso di lasciare perdere quel tipo di ottimizzazione (anche perchè misurando la memoria occupata in ram, non ne valeva la pena) e ho giocato un po' con il css. Lascio in allegato in caso dovesse servire a qualcuno. Grazie a tutti per l'aiuto.

web_server.zip (7.46 KB)

Ho deciso di riprendere in mano il discorso dell'ottimizzazione. Allego due versioni del programma, una non ottimizzata ma funzionante (v1) e una ottimizzata ma non funzionante (v2). Il problema che mi da è che non carica la pagina web utilizzando la versione ottimizzata. Suppongo che sia un problema di memoria ram troppo piena. Ho letto che è possibile salvare degli array di sola lettura (quelli che io ho definito come costanti) nella memoria flash attraverso il tag PROGMEM, ma non ci ho capito niente. Potreste farmi un esempio? Ho solo bisogno di scriverli a inizio programma e di leggerli quando serve. Grazie

WEB SERVER ARDUINO V1.zip (6.55 KB)

WEB SERVER ARDUINO V2.zip (5.44 KB)

antonello94:
... Ho letto che è possibile salvare degli array di sola lettura (quelli che io ho definito come costanti) nella memoria flash attraverso il tag PROGMEM, ma non ci ho capito niente. ...

Prova a studiare il documento che ti allego ... dovresti capire un po' meglio come fare ... :slight_smile:

Guglielmo

Progmem.pdf (182 KB)

No so se funziona anche su Arduino con W5100 ma su ESP8266 i file html si possono zippare usando gzip e usando quindi file tipo index.html.gz

Grazie a tutti ho implementato con successo il tag PROGMEM. Adesso ho un altro problema. Visto che i boolean occupano 8 bit invece che uno, ho deciso di unsare un byte come array di bit. Ho usato le funzioni bitRead(), bitSet(), bitClear(). Però quando vado a compilare ho i seguenti errori

D:\WEB SERVER ARDUINO\WEB SERVER ARDUINO\eth_websrv_SD_Ajax_in_out\eth_websrv_SD_Ajax_in_out.ino: In function 'void loop()':

D:\WEB SERVER ARDUINO\WEB SERVER ARDUINO\eth_websrv_SD_Ajax_in_out\eth_websrv_SD_Ajax_in_out.ino:91:29: warning: array subscript has type 'char' [-Wchar-subscripts]

           HTTP_req[req_index] = c;

                             ^

In file included from C:\Users\ANTONE~1\AppData\Local\Temp\arduino_build_246919\sketch\eth_websrv_SD_Ajax_in_out.ino.cpp:1:0:

D:\WEB SERVER ARDUINO\WEB SERVER ARDUINO\eth_websrv_SD_Ajax_in_out\eth_websrv_SD_Ajax_in_out.ino: In function 'void SetOUTsFromINs()':

C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino/Arduino.h:113:57: error: expression cannot be used as a function

 #define bitClear(value, bit) ((value) &= ~(1UL << (bit)))

                                                         ^

D:\WEB SERVER ARDUINO\WEB SERVER ARDUINO\eth_websrv_SD_Ajax_in_out\eth_websrv_SD_Ajax_in_out.ino:186:11: note: in expansion of macro 'bitClear'

           bitClear(command, i);

           ^~~~~~~~

C:\Program Files (x86)\Arduino\hardware\arduino\avr\cores\arduino/Arduino.h:113:57: error: expression cannot be used as a function

 #define bitClear(value, bit) ((value) &= ~(1UL << (bit)))

                                                         ^

D:\WEB SERVER ARDUINO\WEB SERVER ARDUINO\eth_websrv_SD_Ajax_in_out\eth_websrv_SD_Ajax_in_out.ino:192:9: note: in expansion of macro 'bitClear'

         bitClear(command, i);

         ^~~~~~~~

Più di una libreria trovata per "SPI.h"
Usata: C:\Program
Più di una libreria trovata per "Ethernet.h"
Usata: C:\Program
Più di una libreria trovata per "SD.h"
Usata: C:\Program
Più di una libreria trovata per "EEPROM.h"
Usata: C:\Program


exit status 1
Errore durante la compilazione per la scheda Arduino/Genuino Uno.

lascio in allegato lo sketch

eth_websrv_SD_Ajax_in_out.ino (8.41 KB)

Vai alle righe indicate e ... prova a guardare la riga immediatamente precedente ... scoprirai gli errori :smiley:

Guglielmo

Non mi indica nessuna riga. Non capisco che errore sia

antonello94:
Non mi indica nessuna riga. Non capisco che errore sia

A no ? ? ? ma li leggete i messaggi di errore o li guardate solamente ? ? ? >:(

Questo è il primo:
D:\WEB SERVER ARDUINO\WEB SERVER ARDUINO\eth_websrv_SD_Ajax_in_out\eth_websrv_SD_Ajax_in_out.ino:186:11: note: in expansion of macro 'bitClear'

Questo è il secondo:
D:\WEB SERVER ARDUINO\WEB SERVER ARDUINO\eth_websrv_SD_Ajax_in_out\eth_websrv_SD_Ajax_in_out.ino:192:9: note: in expansion of macro 'bitClear'

In pratica ti dice:
Nome del programma:riga:colonna: note: descrizione dell'errore

... prova a guardare ::slight_smile:

Guglielmo