Cos'è ElectroYou | Login Iscriviti

ElectroYou - la comunità dei professionisti del mondo elettrico

9
voti

Web Stazione Meteo con Arduino [4] - Il circuito definitivo e la comunicazione http

Finalmente trovo un po’ di tempo per scrivere una nuova puntata della mia avventura con Arduino e la Meteorologia , ecco i nuovi sviluppi.
Wheater-duino.png

Wheater-duino.png

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

schema definitivo

schema definitivo

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.

  1. void StampaErrore(int err) : Stampa sull’LCD una frase corrispondente al codice errore passatogli
  2. void StampaLCD (float temp, float umid) : Stampa sull’LCD i valori di temp e umid
  3. 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


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

Web Stazione Meteo con Arduino [6] : Il Sito Web

Web Stazione Meteo con Arduino [7] : Bug Fixing

10

Commenti e note

Inserisci un commento

di ,

Se hai letto tutto il progetto e hai qualche conoscenza di programmazione web, avrai sicuramente capito che lo sketch invia tramite protocollo http una richiesta a una pagina php, la quale estrae i dati contenuti nella richiesta http e li memorizza in un database mysql con opportune query SQL. Tutto il codice lo trovi nelle pagine successive.

Rispondi

di ,

Buongiorno Davide, complimenti per l'ottimo lavoro dal quale mi sono permesso di prendere spunto per il mio progetto. In merito allo sketch riportato sopra le chiedo gentilmente di indicarmi la struttura del codice php (magari l'esempio completo) della pagina "che riceve" i valori letti dai sensori. Non mi è molto chiaro come una pagina php possa "memorizzare", con le sue variabili, i dati inviati tramite la funzione InvioHttP con arduino. Ringrazio anticipatamente per la collaborazione. Saluti Dug

Rispondi

di ,

Così su due piedi non so se arduino possa fare da client ftp, dovresti vedere se è stata scritta qualche libreria. Altrimenti potresti mandare i file via http a una pagina php, che a sua volta compone il file di testo e lo salva sul webserver.

Rispondi

di ,

Ciao, davvero interessante sto Arduino ed EY. Ho una curiosità e visto che nei forum ufficiali non ho trovato risposte... risposte adatte alla mia pessima preparazione in materia Arduino intendo... Tu usi lo shield Ethernet per caricare i dati su un server SQL se non ho capito male ma io chiedo se è possibile fare un'altra cosa e cioè i dati salvarli in formato testuale (datalog.txt) e tramite ftp, inviare il file stesso ogni tot secondi o minuti ad un web server. Complimenti ancora, Alberto

Rispondi

di ,

Guarda soprattutto http://sen.se/nio che è ancora su invito è incredibilmente flessibile, puoi incastrare delle app per manipolare i dati se ti interessa scrivimi che ti mando un invito. electroyou.it@niomix.com Ciao.

Rispondi

di ,

Bel Lavoro!

Rispondi

di ,

Ciao, hai pensato di usare COSM.com o sen.se per archiviare / elaborare / trasformare / rappresentare e inviare notifiche dei dati letti ? Guarda qui sono in realtime. http://arduino.niomix.com http://sen.se/nio

Rispondi

di ,

Ti ringrazio infinitamente, questa tua risposta mi fa molto piacere, a questo punto appena ho 2 minuti liberi da dedicare al mio progettino sperimento quanto appreso. Inoltre attendo la pubblicazione della guida che mi sarà molto utile. Per ora grazie mille. Ciao

Rispondi

di ,

Dopo la tua richiesta ho deciso di pubblicare un quinto post, che fa da appendice al mio progetto e in cui spiega come funziona a grandi linee come funziona una rete e i suoi vari componenti. Ti consiglierei di leggerla appena la pubblicano in homepage gli admin (quindi nel giro di 24/48h), perchè mi pare ti stia confondendo non poco con i vari meccanismi di funzionamento delle reti. In ogni modo nel mio progetto uso il DHCP per la configurazione degli IP di arduino, invece del vecchio metodo con IP statico e ti confermo che puoi connetterti ad un sito tramite il suo nome. La righa che ai riportato risulta commentata perchè se ci fosse necessità di riferirsi ad un sito tramite ip invece che con il suo nome, basterebbe togliere il commento e metterlo alla riga in cui setto serverName con stringa. Per ogni altra domanda dimmi pure, sarò felice di risponderti!

Rispondi

di ,

Ciao, trovo molto interessante il tuo articolo e vorrei utilizzare quello che mi ha insegnato per poter controllare arduino da Web, il problema è che avendo creato il sito di interfaccia su un dominio di tipo miosito@altervista.org non posso dare in pasto ad arduino l'IP del mio sito in quanto altervista non rilascia un IP dedicato per i suoi domini. Controllando per il web cercando una soluzione mi sono imbattuto nel tuo sito dove vedo che ti connetti alla paggina di esempio "sito.it" tramite stringa di testo come se la inserissi nel browser, a questo punto se è vero che tutto questo funziona vorrei capire come fa arduino a decodificare l'IP, da quanto hai spiegato si serve del DHCP interno al router. A questo punto, dato per scontato che il mio sito non ha un IP dedicato e che non posso richieder un IP fisso al mio gestore telefonico, vorrei sapere se secondo te questo tipo di autmatismo funziona. E soprattutto vorrei sapere se è necessario specificare l'indirizzo del route (in quanto ho visto che la stringa "//IPAddress serverName(127,0,0,1);//indirizzo IP del server a cui connettersi" è commentata, ti prego di far luce su tutti i miei dubbi. Grazie mille, Ivan

Rispondi

Inserisci un commento

Per inserire commenti è necessario iscriversi ad ElectroYou. Se sei già iscritto, effettua il login.