Dopo alcuni giorni di test del sensore DHT22, ho visto che le letture erano veritiere e quindi sono passato alla fase due del progetto, L’invio dei dati tramite ethernet.
Indice |
Semplici Cenni Teorici (http)
Per la comunicazione dei dati, ho scelto ovviamente ilprotocollo http, dato che successivamente dovrò collegarmi con un webserver per l’invio dei dati ad un database remoto. Il protocollo http utilizza come standard la porta 80, ma essa può variare in alcuni casi con la porta 8080 o la 8883(Apache con EasyPHP). Ad oggi la versione più utilizzata di questo protocollo è la 1.1, che corregge alcune lacune della versione 1.0 come:
- L'impossibilità di ospitare più siti www sullo stesso server (virtual host)
- Il mancato riuso delle connessioni disponibili
- L'insufficienza dei meccanismi di sicurezza
Per il mio progetto ho deciso di appoggiarmi all’ultima versione (1.1), soprattutto per una questione di compatibilità con alcuni webserver, che rifiutano i protocolli più vecchi per motivi di sicurezza.
La sintassi http
Una richiesta http si compone di 3 parti:
- Request (riga di richiesta)
- Body (corpo del messaggio)
- Header (informazioni aggiuntive)
Request : questa parte si compone di: Metodo, URI, Versione Protocollo.
- METODO:I metodi più utilizzati sono 3 : GET (recupero informazioni),POST (invio informazioni), HEADER (come GET, ma riceve solo l’intestazione della pagina).
- URI :(Uniform Resource Identifier) è tipo l’URL di una pagina, cioè il suo indirizzo, ma più generico, in pratica è una stringa che identifica univocamente una risorsa generica.
- Versione Protocollo : Nel nostro caso “HTTP/1.1”.
Body : Contiene la richiesta vera e propria.
Header : Contiene varie informazioni, per il nostro scopo utilizzeremo l’header “HOST:” perché richiesto dalla versione 1.1 del protocollo.
Esempio di Richiesta HTTP di tipo GET
“GET www.miosito.it/index.html HTTP/1.1 HOST: www.server.com”
Questa vuole essere una semplice introduzione al protocollo http, così da comprendere la sintassi della richiesta che andrò ad utilizzare nel programma di Arduino. Per ogni approfondimento, rimando alla lettura della sitografia.
Shield Ethernet
Lo shield ethernet che andrò ad utilizzare sarà quello ufficiale, basato sul controller ethernet Wiznet W5100, che è in grado di gestire funzionalità di rete IP con i protocolli TCP e UDP, è anche in grado di gestire fino 4 connessioni simultanee. Questo shield offre un’altra funzione oltre alla comunicazione Ethernet ed è la lettura/scrittura su un MicroSD, purtroppo questi due componenti dello shield condividono le porte di comunicazione, quindi se abbiamo intenzione di utilizzarli nello stesso programma dobbiamo avere l’accortezza di disabilitare/disabilitare l’uno o l’altro a seconda di ciò che intendiamo usare in quel momento.
La libreria Ethernet
Per gestire lo shield Ethernet, ci viene incontro la libreria ethernet dell’universo Arduino. Analizziamo i comandi principali:
Ethernet.begin(mac) : Questo è il comando che setta gli indirizzi dello shield, con la versione 1.0 delle librerie, è stato implementato il supporto al protocollo DHCP, per la configurazione automatica degli indirizzi IP. Dato che ormai da alcuni anni ogni router/modem integra in se un semplice server DHCP, possiamo tranquillamente appoggiarci a questa funzione così da non dover riprogrammare la scheda per cambiare IP.
Client.connect(server,porta): Con questo comando, si stabilisce la connessione al server di cui si fornisce l’indirizzo o il nome (che verrà poi risolto in automatico tramite DNS) e la porta a cui connettersi. Stabilita la connessione è possibile inviare e ricevere i dati.
Client.print(char[])/ Client.println(char[]): Sono i principali comandi che vengono utilizzati per comporre i dati da inviare tramite la comunicazione ethernet. Il comando println differisce dal comando print, per l’aggiunta in coda al messaggio di un Line Feed, cioè un “a capo”.
Hardware
Andiamo ad analizzare la parte hardware del progetto.
Occupazione Pin
Come sappiamo, la scheda Arduino Uno presenta 14 Pin digitali, due dei quali (0 e 1), sono in comune al controller USB, quindi risultano inutilizzabili se si decide includere nel programma una sorta di DEBUG via USB. Nel caso in cui non si implementi nel programma la comunicazione seriale via USB possono essere usati, a patto che durante l’upload del programma ogni cosa connessagli venga scollegata per evitare errori di programmazione del microcontrollore.
Pin n. | Compito |
---|---|
0 | Rx (Condiviso con USB) |
1 | Tx (Condiviso con USB) |
2 | RS (LCD) |
3 | E (LCD) |
4 | Enable per MicroSD |
5 | D4 (LCD) |
6 | D5 (LCD) |
7 | D6 (LCD) |
8 | D7 (LCD) |
9 | Data In Sensore |
10 | Enable per Controller Ethernet |
11 | MOSI (SPI) |
12 | MISO (SPI) |
13 | SCK (SPI) |
Lo Schema
L’immagine permette di capire le varie connessioni tra icomponenti, da notare che le connessioni marcate di Blu rappresentano i pinutilizzati dalle scheda ethernet e sono realizzate con la semplice interconnessionedi arduino con la stessa. Nello schema trovano posto:
- 1x schermo LCD 16x1
- 1x potenziometro 10 kohm (per regolare il contrasto dell’LCD)
- 1x resistenza 1 kohm (resistenza di Pull-Up delsensore)
Lo Sketch
Eccoci arrivati al programma vero e proprio.
#include <dht.h>//includo la libreira per il sensore #include <LiquidCrystal.h>//includo la libreria per l'LCD #include <SPI.h> #include <Ethernet.h> #define DHT22_PIN 9 //imposto il pin a cui è connesso il sensore #define Periodo_Invio_Dati 300000 //tempo minimo tra un'invio sul web e l'altro.(ms) #define Periodo_Lettura_Sensore 2000 //tempo minimo tra una lettura del sensore e l'altra (ms) #define id_sensore_temp 1 #define id_sensore_umid 2 char username[] = "";//username per login sito char password[] = "";//password per login sito byte mac[] = { 0x00, 0x22, 0x11, 0x00, 0x22, 0x11};// Mac address shield Etherne, si trova su di un adesivo sotto lo shield //IPAddress serverName(127,0,0,1);//indirizzo IP del server a cui connettersi char serverName[] = "sito.it";//URL del server a cui connettersi #define serverPort 80 //porta di connessione char pageName[] = "pagina.php";//nome pagina php per la ricezione dei dati dht DHT; //dichiaro una variabile globale per la classe del sensore EthernetClient client;//dichiaro una variabile globale per il client ethernet LiquidCrystal lcd(2, 3, 5, 6, 7, 8); //dichiaro la variabile lcd definendo i pin utilizzati double accum_temp = 0.0; double accum_umid = 0.0; long n_camp = 0; float avg_umid = 0.0; float avg_temp = 0.0; unsigned long time = 0; unsigned long SendTime = 0; unsigned long ReadTime = 0; void setup() { lcd.begin(8, 2); //inizializzo l'LCD // start the serial library: Serial.begin(9600); // start the Ethernet connection: Serial.println("Configuro la connessione con DHCP..."); while(Ethernet.begin(mac) == 0)//configuro lo shield ethernet con il DHCP { Serial.println("Errore Configurazione, ritento tra 3 min"); delay(180000);//se la configurazione non va a buon fine, attendo 3 min e ritento. } Serial.println("Connessione Configurata."); delay(1000);//aspetto un secondo per far avviare lo shield ethernet Serial.println("Programma Avviato, Setup Terminato"); } void loop() { time = millis(); if(time > SendTime + Periodo_Invio_Dati) { SendTime = millis(); avg_temp = float(accum_temp / double(n_camp));//calcolo la media delle lettura avg_umid = float(accum_umid / double(n_camp)); if(n_camp > 0) { Serial.println("connessione..."); Serial.println(serverName); if (client.connect(serverName, serverPort)) //connessione al server per invio lettura sensore temperatura { Serial.println("connesso"); Serial.println("inizio primo invio"); InvioHttp(serverName,serverPort,pageName,username,password,id_sensore_temp,avg_temp); client.stop(); Serial.println("Client Disconnesso"); Serial.println("fine primo invio"); if (client.connect(serverName, serverPort)) { Serial.println("inizio secondo invio"); InvioHttp(serverName,serverPort,pageName,username,password,id_sensore_umid,avg_umid); Serial.println("fine secondo invio"); client.stop(); Serial.println("Client Disconnesso"); } else Serial.println("Errore Seconda Connessione"); } else Serial.println("Errore Prima Connessione"); } else Serial.println("Nessuna Campionatura, controllare sensore"); n_camp = 0; //azzero le variabili per iniziare nuovamente il calcolo della media accum_temp = 0.0; accum_umid = 0.0; } if(time > ReadTime + Periodo_Lettura_Sensore) { ReadTime = millis(); int chk = DHT.read22(DHT22_PIN);//leggo dal sensore e ritorno un valore corrispondente all'esito della lettura if(chk == 0) { accum_temp += DHT.temperature; accum_umid += DHT.humidity; n_camp++; StampaLCD(DHT.temperature,DHT.humidity); Serial.print("Campione : "); Serial.print(n_camp); Serial.print(" - "); Serial.println(time); } else { StampaErrore(chk); Serial.println("Errore Campionamento"); } } }
Oltre a questa partedi codice ho implementato tre funzioni che vanno a eseguire le parti piùripetitive del codice.
- void StampaErrore(int err) : Stampa sull’LCD una frase corrispondente al codice errore passatogli
- void StampaLCD (float temp, float umid) : Stampa sull’LCD i valori di temp e umid
- void InvioHttp (char server[], int porta, char pagina[], char username[], char password[], int idSensore, float dato) : Compone ed invia la richiesta http per comunicare una lettura.
void InvioHttp (char server[], int porta, char pagina[], char username[], char password[], int idSensore, float dato) { client.print("GET http://");// GET http://'server':'porta'/'pagina'?username=nome_utente&password=password&sensore=numero_sensore&dato=lettura_sensore HTTP/1.0 client.print(server); client.print(":"); client.print(porta); client.print("/"); client.print(pagina); client.print("?username="); client.print(username); client.print("&password="); client.print(password); client.print("&sensore="); client.print(idSensore); client.print("&dato="); client.print(dato); client.println(" HTTP/1.1"); client.print("Host: "); client.println(server); client.println(); }
Come avevo accennato nelle puntate precedenti, ho implementato un sistema di temporizzazione delle azioni, che scimmiotta un po’ il multitasking di un pc, ma che non arriva a eseguire due compiti alla volta. Questo metodo è basato sulla funzione millis(), che restituisce il numero di millisecondi passati dall’avvio del programma, e su delle variabili (una per ogni “pseudo-processo”) che contengono il momento in cui la funzione a cui sono associate è stata eseguita per l’ultima volta. Utilizzando la seguente condizione, si può decidere se il comando debba essere eseguito in quel momento o no:
Tempo > UltimaEsecuzione + PeriodoEsecuzione
- Tempo : Contiene i numero dei millisecondi passati dall’avvio di Arduino e viene aggiornata all’inizio di ogni ciclo della funzione loop().
- UltimaEsecuzione : Gli viene assegnato il valore di millis() ad ogni esecuzione della funzione a cui è associata.
- PeriodoEsecuzione : Contiene il valore in millisecondi del tempo minimo che deve trascorrere tra un’esecuzione e l’altra della funzione a cui è associata.
Debugging
Parliamo un secondo del debugging di questo programma. Per quanto riguarda la parte di codice ci possiamo appoggiare alla classe Serial, che permette di scrivere e leggere sulla porta USB di arduino, inseriamo quindi in alcuni punti critici dei messaggi sullo stato del programma, che poi possiamo leggere tramite la funzione SerialMonitor inserita nel compilatore di arduino. Il problema più grande è dato dal debugging della parte di comunicazione ethernet. Per far ciò ho utilizzato una suite opensource per lo sviluppo di siti dinamici in php, che utilizzerò anche successivamente per lo sviluppo del sito internet per la raccolta dei dati, sto parlando di EasyPHP. Questa suite incorpora un WebServer (Apache), un gestore di database(MySQL) e un’ interfaccia grafica per configurare il database (PhpMyAdmin), per adesso ci limitiamo a utilizzare Apache e più precisamente i suoi file di Log
File di Log
Per assicurarci che la comunicazione avvenga correttamente dobbiamo controllare che Apache riceva la richiesta http, che la forma della richiesta sia corretta e che il WebServer non restituisca degli errori in risposta. Per far ciò, a seguito di un tentativo di connessione di Arduino, andiamo a leggere il file di Log degli errori che ci dirò se è arrivata una richiesta di connessione e perché è stata rifiutata, di conseguenza apporteremo le dovute correzioni. Quando il tutto sarà funzionante, troveremo nel Log degli Accessi, la stringa di richiesta http che ha inoltrato Arduino.
Conclusione
Per adesso è tutto, la prossima volta parleremo della raccolta dei dati nel database e del sito per recuperare i dati. Alla prossima Puntata !
Sitografia
- Protocollo http (1): http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html
- Protocollo http (2): http://it.wikipedia.org/wiki/Hyper_Text_Transfer_Protocol
- Shield Ethernet : http://arduino.cc/en/Main/ArduinoEthernetShield
- Ethernet Library : http://arduino.cc/en/Reference/Ethernet
- Arduino Serial : http://arduino.cc/en/Reference/serial
- EasyPHP : http://www.easyphp.org/
Link agli articoli del progetto
Web Stazione Meteo con Arduino [1] - L'idea
Web Stazione Meteo con Arduino [2] - La Rilevazione dei Dati
Web Stazione Meteo con Arduino [3] - Il Primo Circuito di Prova
Web Stazione Meteo con Arduino [5] - Appendice 1 : Le Reti spiegate a mia Nonna