Arduino Webserver: ecco a voi cosa è uscito!

ciao a tutti!
Non so se è la sezione giusta per postare del codice..

ad ogni modo sono entrato da poco nel mondo di arduino, sono studente universitario di sicurezza informatica e ho deciso di migliorare le mie capacità di programmazione (sono a livelli davvero elementari e conosco un solo linguaggio di programmazione, il C) e scrivere un webserver per arduino (con ethernet shield) che mi potesse servire anche come progetto per qualche esame universitario.

eccomi qui quindi, orgoglioso del mio lavoro, vi do in pasto il codice, anche se non è ancora ottimizzato e puo sembrare abbastanza disordinato, soprattutto perchè l'ho commentato davvero tantissimo in previsione di doverlo mostrare in dieci minuti a qualche annoiato professore.

Fondamentalmente le cose che fa sono poche:

-implementa la gestione dell'errore 404

-mostra una homepage nella quale inserire dei dati opportunamente formattati, che verranno poi loggati e mostrati in un'altra pagina, in ordine cronologico inverso di inserimento

-possibilità di cancellare il log

-gestione dell errore 401, nel caso si tentasse si aggiornare il log con un ID scorretto (una sorta di mini autenticazione)

-prelievo delle stringhe dati immesse tramite form html (modalità GET) tramite uno pseudo-cgi che va a leggermi la stringa e estrapolare solo le parti a noi utili.

-cosa più importante: data l'esigua disponibilità di RAM dell'arduino, sono stato costretto a mappare il caricamento degli array contenenti il codice html in ROM invece che in RAM (tramite comando progmem), e quindi a implementare un macchinoso metodo per sputare fuori l'intera stringa html pezzo per pezzo, caricandola in RAM in un buffer di 128 celle.. così pian piano sendo tutto al client senza saturare la RAM. Devo dire che funziona egregiamente.

Vi posto gli screenshot delle pagine (appena la board mi autorizzerà), e a seguire il codice (uso notepad++ quindi non so cosa verrà fuori a incollare il codice qui!)

le pagine sono mal formattate e molto scarne, ma il concetto è quello, uno è poi libero di abbellire come meglio crede.

Il codice è lungo quindi lo dovrò spezzettare in più post, credo.
Suggerimenti graditi.. è il mio primo programma complesso quindi sicuramente sarà pieno di strafalcioni.

ciao a tutti!

#include <Ethernet.h>
#include <avr/pgmspace.h>
#include <string.h>




byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
byte ip[] = { 192, 168, 0, 44 };
byte gateway[] = { 192, 168, 0, 3 };


char htmlOUT[128];                        //buffer di uscita stringa HTML: carico 128 bytes in RAM alla volta, leggendoli dalla ROM indirizzata con PROGMEM (tutto ciò perchè HO SOLO 1k di RAM!!!!!!)
char httpIN[128];                        //buffer da riempire con la prima riga del GET
char logg[8][53];                      //controllare se li azzera alla creazione o sono valori casuali! (sembra li azzeri..)
unsigned int x=0;                        //variabile che servirà per decidere in che riga dell'array dei log scrivere-leggere (dichiarata unsigned così quando overflowa rimane in positivo 0-65535)



//dichiaro e riempio in progmem le stringhe HTML, ne calcolo la lunghezza (con strlen_P, perchè sono in ROM!)

PROGMEM prog_char home[]= "HTTP/1.0 200 OK\nServer: arduino\nCache-Control: no-store, no-cache, must-revalidate\nPragma: no-cache\nConnection: close\nContent-Type: text/html\n\n<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /><title>Arduino Web Server - Invia i tuoi dati</title></head><body><h1>Benvenuto. Inserisci i tuoi dati:</h1>

<form action=\"submit\" method=\"get\"> Id client: <input type=\"text\" name=\"id\" value=\"\" />  Wan Ip: <input type=\"text\" name=\"ip\" value=\"192168000001\" />  Ora: <input type=\"text\" name=\"time\" value=\"2344\" />  Data: <input type=\"text\" name=\"data\" value=\"04072009\" />  <INPUT TYPE=submit VALUE=\"Invia\"></form>

<a href=\"read\">Consulta il log</a> <a href=\"clear\">Cancella il registro di log</a>

<a href=\"mailto:xxx@xxx.it\">mailto</a></body></html>";
int home_l = strlen_P(home);

PROGMEM prog_char ok[]= "HTTP/1.0 200 OK\nServer: arduino\nCache-Control: no-store, no-cache, must-revalidate\nPragma: no-cache\nConnection: close\nContent-Type: text/html\n\n<html><head><title>Arduino Web Server - Thank you</title></head><body><h1>Invio dei dati avvenuto con successo.</h1>

<a href=\"read\">Consulta il log</a> <a href=\"home\">Vai alla Homepage</a></body></html>";
int ok_l = strlen_P(ok);

PROGMEM prog_char read[]= "HTTP/1.0 200 OK\nServer: arduino\nCache-Control: no-store, no-cache, must-revalidate\nPragma: no-cache\nConnection: close\nContent-Type: text/html\n\n<html><head><title>Arduino Web Server - Read</title></head><body><h1>Registro di log:</h1>

";
int read_l = strlen_P(read);

PROGMEM prog_char clear[]= "HTTP/1.0 200 OK\nServer: arduino\nCache-Control: no-store, no-cache, must-revalidate\nPragma: no-cache\nConnection: close\nContent-Type: text/html\n\n<html><head><title>Arduino Web Server - Clear</title></head><body><h1>Registro di log cancellato.</h1>

<a href=\"home\">Vai alla Homepage</a></body></html>";
int clear_l = strlen_P(clear);

PROGMEM prog_char p401[]= "HTTP/1.0 401 Authorization Required\nServer: arduino\nContent-Type: text/html\n\n<html><head><title>Arduino Web Server - Errore 401</title></head><body><h1>Errore 401: Non sei autorizzato!</h1></body></html>";
int p401_l = strlen_P(p401);

PROGMEM prog_char p404[]= "HTTP/1.1 404 Not Found\nServer: arduino\nContent-Type: text/html\n\n<html><head><title>Arduino Web Server - Errore 404</title></head><body><h1>Errore 404: Pagina non trovata!</h1></body></html>";
int p404_l = strlen_P(p404);

//////////////////////////////////////////////////////////////////////////

Server server(80);                        //inizializzo arduino, ciao bel!

void setup() {
  Ethernet.begin(mac, ip, gateway);            
  server.begin();                  //al lavoro!
}

//////////////////////////////////////////////////////////////////////////

void loop() {

  Client client = server.available();
  if (client) {                                                 //quando client è connesso e ha roba da mandare vale 1, quindi inizia il codice
      
  int indicebuffer = 0;                                     //al successivo comando GET azzero l'indice che mi servirà per scandire httpIN: lo devo rileggere da capo!

  
  
  if (client.connected() && client.available()) {                   //se il client è connesso, allora.......
    httpIN[0] = client.read();                                    //leggo i primi due caratteri, così ho gia tutto pronto per il primo confronto: voglio continuare a riempire httpIN finchè non trovo \n seguito da \r ==> fine riga!
    httpIN[1] = client.read();
    indicebuffer = 2;                        
    while (httpIN[indicebuffer-2] != '\r' && httpIN[indicebuffer-1] != '\n') {
      if (indicebuffer<128) httpIN[indicebuffer] = client.read();                              //continuo a riempire.....
      indicebuffer++;
    }
      
      
      //scelgo che pagina printare in base all'uri (comparo tutta la stringa GET)
      
      if ((strncmp(httpIN, "GET / ", 6) == 0) || (strncmp(httpIN, "GET /home ", 10) == 0)) {                                                //homepage            
            stampapagina(client, home, home_l);
            }
            
    else if ( strncmp(httpIN, "GET /read ", 10) == 0 ) {                                                            //pagina di lettura log
            stampapagina(client, read, read_l);
            for(int y=1; y<9; y++) {
                  client.print(logg[(x-y)%8]);                        //printo tutte le righe dell'array di log, partendo dalla più nuova e andando a ritroso
                  client.print("
");
            }
            client.print("

<a href=\"home\">Vai alla Homepage</a> <a href=\"clear\">Cancella il registro di log</a></body></html>");   //chiudo l'html!
            }
            
      else if ( strncmp(httpIN, "GET /clear ", 11) == 0 ) {                  
            for(int k=0; k<8; k++) logg[k][0]='\0';                                                //pagina per cancellare il log
            stampapagina(client, clear, clear_l);      
            }
            
      //  GET /submit?id=001&ip=192168000001&time=2344&data=04072009       ---->       ID:001 Ip:192.168.000.001 Time:23.44 Data:04/07/2009
            
      else if (strncmp(httpIN, "GET /submit", 11) == 0) {                  //se mi arriva submit.. allora..
            if ((strncmp(&httpIN[15], "001", 3) == 0) || (strncmp(&httpIN[15], "002", 3) == 0))  {                  //..allora controllo di essere autorizzato, ovvero se id client è tra quelli permessi 
                        
                  strcpy(logg[x], "ID:");                        strncat(logg[x], &httpIN[15], 3);
                  strcat(logg[x], " Ip:");                  strncat(logg[x], &httpIN[22], 3);
                  strcat(logg[x], ".");                        strncat(logg[x], &httpIN[25], 3);
                  strcat(logg[x], ".");                        strncat(logg[x], &httpIN[28], 3);
                  strcat(logg[x], ".");                        strncat(logg[x], &httpIN[31], 3);      //leggo da httpin e butto dentro nella riga attuale (determinata da x) dell'array di log tutti i dati da loggare
                  strcat(logg[x], " Time:");                  strncat(logg[x], &httpIN[40], 2);
                  strcat(logg[x], ".");                        strncat(logg[x], &httpIN[42], 2);
                  strcat(logg[x], " Data:");                  strncat(logg[x], &httpIN[50], 2);
                  strcat(logg[x], "/");                        strncat(logg[x], &httpIN[52], 2);
                  strcat(logg[x], "/");                        strncat(logg[x], &httpIN[54], 4);
                        
                  strcat(logg[x], '\0');                  //non mi fido di strncat, non so se mette il terminatore, quindi lo metto io alla fine, tiè! 
                        
                  x=x+1;      //a fine riempimento riga aumento x, così la volta dopo mi riempie la riga seguente, o..
                  if(x==8) x=0;                  //..o ricomincia da capo se è arrivato in fondo all'array
                  
                  stampapagina(client, ok, ok_l);                        //ecco a lei il registro di log, signore!      
                  }
                        
            else stampapagina(client, p401, p401_l);  //se il mio id client non è tra quelli permessi --> errore 401 not authorized
            }
            
    else stampapagina(client, p404, p404_l);     //se scrivo qualsiasi altra cosa come uri --> errore 404, la pagina non esiste
      }

    delay(5);                                                        //mi fermo, prima di chiudere la tcp gli faccio ricevere tutto (verificare lunghezza "sicura" per timeout..)
    client.stop();                              //byebye alla prossima!
      }

}

//////////////////////////////////////////////////////////////////////////

//funzione per stampare la pagina

void stampapagina(Client client, PGM_P nome_originale, int lunghezza_originale) {                  //ricordo che il puntatore a spazio ROM indirizzato va dichiarato non come int ma come PGM_P!
      
      int indice = 0;  //creo l'indice che mi servirà per contare quanti caratteri ho complessivamente caricato dalla stringa iniziale (così blocco quando li ho caricati tutti)
      
      //riempio 127+1 caratteri alla volta (occhio a PPROGMEM...!!!)
      
      while (indice < lunghezza_originale) {                  //fin quando non ho caricato tutti i caratteri dell'array progmem, continuo a riempire il buffer 128 celle alla volta
            int n_for = 0;                                          //inizializzo a zero il contatore dei cicli for eseguiti, ogni volta che ricomincio a riempire il buffer da 128
            for(int i=0; i<127; i++) {
                  htmlOUT[i] = pgm_read_word(&(nome_originale[indice]));  //si usa pgm_read_word(&(cella array in progmem da leggere))   <--& va perchè altrimenti gli darebbe il VALORE della cella e non l'indirizzo.
                  indice++; n_for++;                  //incremento indice e contatore for, così vado avanti a leggere l'array in progmem e tengo anche conto di quanti for ho eseguito (ciascuna volta che riempio il buffer)
                  if (indice == lunghezza_originale) break;                  //fermi tutti! se la stringa finisce prima di colmare il buffer, mi fermo (lunghezza html non multipla di lunghezza buffer)
                  }                         
            htmlOUT[n_for] = '\0';                  //butto nella cella successiva a quella dell ultimo carattere copiato nel buffer il terminatore di stringa, utile (penso) alla funzione client.print
            client.print(htmlOUT);                   //spedisco i 128 caratteri dell'array_s (127 di payload e 1 di terminatore) al mio client
            }
}

c'è stato! ciao a tutti!

Bravissimo Marco.
Puoi anche mettere il tuo post (ma in inglese) nella pagina Exibition del forum.
Complimenti.
Con qualche limatura potrebbe diventare un bel progettino.