Ethernet Shield

Ethernet Shield

Altro passo è stato quello di procurarmi una ethernet shield per arduino come questa:

 

che assemblata assieme ad arduino è così composta:

E a cosa serve questo?

Beh, è semplice. L'idea è di acquisire i dati dai sensori e oltre che visualizzarli sul display poter caricarli su una pagina web e visualizzarli, vedere l'andamento durante il giorno, esportarli e renderli pubblici.

L'argomento è spiegato bene su questa pagina da dove ho preso spunto. Io posto i vari test effettuati da me per comprendere come funziona l'ethernet shield e per capire come comunicare con un server. Gia gli esempi di uso propri della libreria sono ottimi per iniziare a capire il funzionamento.

Questo è il primo sketc:

#include <Ethernet.h>

#include <SPI.h>

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x4D, 0xAF }; //indirizzo mac della scheda

char serv[]="nome del server";
int porta= 80;
char c;

EthernetClient client; //dichiaro una variabile globale per l'ethernet shield

void setup()
{
   Serial.begin(9600);
   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()
{
   Serial.println("connecting...");
   if (client.connect(serv, 80)) {
      c = client.read();
      Serial.println(c);
      Serial.println("connected");
      if (client.available()) {
         c = client.read();
         Serial.println(c);
      }
      else Serial.println ("lettura non avvenuta");

      Serial.println("ora trasmetto");
      char Stringa[]="GET http://nome del server/ricezione.php?username=username&password=password&sensore=1&dato=50 HTML/1.1";
      client.println(Stringa);
      client.println();
      Serial.println("trasmesso");

   }
   else {
      Serial.println("connection failed");
   }

   if (client.available()) {
      c = client.read();
      Serial.println(c);
   }
   else Serial.println ("lettura non avvenuta");

   if (client.connected()) {
      Serial.println();
      Serial.println("disconnecting.");
      client.stop();
      Serial.println("client stop");
   }
   delay(10000);
}

Questo breve skatc si connette al server, comunica una stringa e poi chiude una connessione. Viene passato il nome utente e la password, il numero del sensore e il dato. 

A lato server c'è bisogno di una pagina di ricezione scritta in PHP che permetta di acquisire e caricare su un database i dati inviati dal sensore.

Una bozza di pagina è la seguente:

<?php

$mypassword=$_GET['password'];
$myusername=$_GET['username'];

$host="192.168.1.1"; // Host name
$username="MYsql"; // Mysql username
$password="password"; // Mysql password
$db_name="nome"; // Database name
// Connect to server and select databse.
mysql_connect("$host", "$username", "$password")or die("cannot connect");
mysql_select_db("$db_name")or die("cannot select DB");

$sql="SELECT * FROM utente WHERE username = '$myusername' AND password='$mypassword'"; //controllo che l’utente esista
$result = mysql_query($sql);
$count=mysql_num_rows($result);

if($count==1)
{
   $array=mysql_fetch_array($result);
   $id_utente=$array['id_utente']; //recupero l’id_utente
   //inserire contenuto pagina
   if(isset($_GET['sensore'])) //se la variabile è stata impostata
   {
      $id_sensore = $_GET['sensore'];
      if(isset($_GET['dato']) && is_numeric($_GET['dato']))
      {
         $valore = $_GET['dato'];
         $sql="SELECT * FROM sensore WHERE id_utente = '$id_utente' AND id_sensore = '$id_sensore'"; //controllo che esista il sensore
         $result = mysql_query($sql);
         $count=mysql_num_rows($result);

         if($count==1)
         {
            $sql1="INSERT INTO lettura SET id_sensore = '$id_sensore', lettura='$valore'"; //inserisco il dato
            if(mysql_query($sql1))
               echo("ok");
            else
               echo ('errore db');
         }
         else
            echo("codice sensore errato");
      }
      else
         echo("formato valore non corretto");
   }
   else
      echo("codice sensore mancante");
}
else
{
   echo "<h1>Area riservata - accesso negato</h1>";
   die;
}
mysql_close();//chiudo la connessione
ob_end_flush();
?>

 

Lato server deve essere poi creato un database con questi campi:

L'immagine è presa da qui. Come fare a creare un database con queste carateristiche?
Beh, girando in rete si trova qualcosa che spiega.

Adesso viene il bello di rendere automatizzata la cosa e caricare i dati in automatico.

Dai dati acquisiti dai sensori, dopo aver fatto eventuali medie o altre elaborazioni, è bene costruire una funzione che lavori in automatico.

Io inizialmente ho usato questa:

void InvioHttp (char server[], char pagina[], char username[], char password[], int idSensore, float dato)

{
  // GET http://'server':'porta'/'pagina'?username=nome_utente&password=password&sensore=numero_sensore&dato=lettura_sensore HTTP/1.0
  //questa è usata per scriverlo da riga browser per vedere se va bene la pagina php

  client.print("GET /");
  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();
}

Le limitazioni sono evidenti, però passo dopo passo è possibile aumentare la complessità.

I dati che devono essere caricati sono molti poi e quindi è meglio inserirli in un array e processarli con un ciclo for. 

Altra cosa da dire è che il metodo GET non è molto sicuro. È meglio usare il metodo POST.

La funzione successiva è stata questa:

 void InvioHttpSeriale (String server, String pagina, String username, String password, int id[], float dati[], int len){

  String data = "username=" + username + "&password=" + password + "&ndati=" + String(len); //va aggiunto il numero di dati

  for(int i=0; i<len ; i++) //len=numero dati
    data += "&sensore" + String(i) + "=" + String(id[i]) + "&dato" + String(i) + "=" + String(printDouble(dati[i],2));

  String request = "POST /" + pagina + " HTTP/1.1";
  String headerHost = "Host: " + server;
  client.println( request );
  client.println( headerHost );
  client.println( "Content-Type: application/x-www-form-urlencoded" );
  client.println( "Connection: close" );
  client.print( "Content-Length: " );
  client.println( data.length() );
  client.println();
  client.print( data );
  client.println();

  Serial.print("data.lenght= "); //oltre a mandare i dati voglio anche vederli stampati in via seriale
  Serial.println(data.length());
  Serial.println(data);
}

I dati devono essere inviati sottoforma di stringa per cui bisogna trasformare tutti i numeri in stringa e poi devo troncare eventuali cifre decimali dovute alle medie effettuare:

String printDouble( double val, byte precision){

  // prints val with number of decimal places determine by precision
  // example: printDouble( 3.1415, 2); // prints 3.14 (two decimal places)

  String alfa=String(int(val)); //prints the int part and the decimal point
  if (precision>0){
    alfa+="."; // print to string the decimal point
    unsigned long pass,mult=1;
    byte padding = precision -1;
    while(precision--)
    mult *=10;

    if(val >= 0)
      pass=mult*(val - int(val)); 
    else
      pass = (int(val) - val) * mult;

    unsigned long frac1 = int(pass);

    while(frac1 /= 10)
      padding--;

    while(padding--)
      alfa+="0";
    alfa+=String(int(pass));
  }
return(alfa);
}

A questo punto dobbiamo mettere assieme tutti i vari pezzetti di programma e decidere ogni quanto tempo inviare i dati. Io ho deciso di inviarli ogni 5 minuti. I dati di temperatura e umidità verranno mediati, il dato di pressione verrà preso in considerazione l'ultimo letto.

Il seguente è un po il primo programma completo:

#include <dht.h>//includo la libreira per il sensore
#include <LiquidCrystal.h>//includo la libreria per l'LCD
#include <SPI.h>
#include <Ethernet.h>
#include <Wire.h>
#include <Adafruit_BMP085.h>

#define DHT22_PIN 9 //imposto il pin a cui è connesso il sensore
#define Periodo_Invio_Dati 15000 //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

byte mac[] = { 0x90, 0xA2, 0xDA, 0x0D, 0x4D, 0xAF}; // Mac address shield Etherne, si trova su di un adesivo sotto lo shield
byte server[] = { 62, 149, 142, 110 }; //indirizzo ip del server
String sito= "www....mettete la vostra pagina"; //URL del server a cui connettersi
int porta= 80; //porta di connessione
String pagina="ricezione_dati.php"; //nome pagina php per la ricezione dei dati
String username="username";
String password="password";
int idSensore[]= {0,1,2,3,4}; //prevedo di inserire più sensori
float dato[]= {0,0,0,0,0}; //inizializzo l'arrai che conterrà i dati da inviare al server
char c;

Adafruit_BMP085 bmp;
dht DHT; //dichiaro una variabile globale per la classe del sensore
EthernetClient client; //dichiaro una variabile globale per il client ethernet
LiquidCrystal lcd(7, 6, 5, 4, 3, 2); //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;
float t,u;

unsigned long time = 0;
unsigned long SendTime = 0;
unsigned long ReadTime = 0;

void setup() {
  lcd.begin(16, 2); //inizializzo l'LCD
  // start the serial library:
  Serial.begin(9600);
  bmp.begin();
  Serial.println("Configuro la connessione con DHCP..."); // start the Ethernet connection
  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.");
  dato[0] = float(bmp.readPressure()/100);
  delay(1000);//aspetto un secondo per far avviare lo shield ethernet
  Serial.println("Programma Avviato, Setup Terminato");
}

void loop() {
  time = millis();

  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);
      StampaLCD(chk);
      //Serial.println(accum_temp);
      //Serial.println(accum_umid);
    }
    else {
      Serial.println("Errore Campionamento");
    }
  }

  if(time > SendTime + Periodo_Invio_Dati) {
    SendTime = millis();
    dato[1] = float(accum_temp / double(n_camp)); //calcolo la media delle lettura
    dato[2] = float(accum_umid / double(n_camp));
    Serial.println("media");
    Serial.println(dato[1]);
    Serial.println(dato[2]);
    dato[0] = float(bmp.readPressure()/100);
    Serial.print("Pressure = ");
    Serial.print(dato[0]);
    Serial.println(" mbar");

    if(n_camp > 0) {
      Serial.println("connessione...");
      if (client.connect(server, porta)) //connessione al server per invio lettura sensore temperatura
      {
        Serial.println("connesso");
        InvioHttpSeriale(sito, pagina, username, password, idSensore, dato, 3);
        client.stop();
        Serial.println("trasmesso");
      }
      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;
  }


//StampaLCD();

}

Le funzioni usate sono le seguenti:

void InvioHttpSeriale (String server, String pagina, String username, String password, int id[], float dati[], int len){

  String data = "un=" + username + "&pw=" + password + "&ndati=" + String(len); //va aggiunto il numero di dati

  for(int i=0; i<len ; i++){ //len=numero dati
    data += "&s" + String(i) + "=" + String(id[i]) + "&d" + String(i) + "=" + String(printDouble(dati[i],2));
  }

  String request = "POST /" + pagina + " HTTP/1.1";
  String headerHost = "Host: " + server;
  client.println( request );
  client.println( headerHost );
  client.println( "Content-Type: application/x-www-form-urlencoded" );
  client.println( "Connection: close" );
  client.print( "Content-Length: " );
  client.println( data.length());
  client.println();
  client.print( data );
  client.println();

  //Serial.print("data.lenght= ");
  //Serial.println(data.length() + stamp.length());
  Serial.println(data);

}

 

 

void StampaLCD(int chk) {
  switch (chk)//controllo
  {
    case 0: //valore di ritorno : 0 -> Lettura andata a buon fine
      lcd.setCursor(0,0);//stampo sull'lcd i dati letti
      lcd.print("T:");
      lcd.print(DHT.temperature,1);//scrivo il dato della temperatura, specificando che voglio un solo decimale
      lcd.print("C");
      lcd.setCursor(8,0);

      lcd.print("U:");
      lcd.print(DHT.humidity,1);//scrivo il dato dell'umiditò, specificando che voglio un solo decimale
      lcd.print("%");

      lcd.setCursor(0,1);
      lcd.print("P:");
      lcd.print(dato[0]);
      lcd.print("mbar ");

    break;


    case -1: //valore di ritorno : -1 -> Sono stati letti dei dati, ma risultano corrotti
      lcd.print("Checksum error");
    break;

    case -2: //valore di ritorno : -2 -> Nessuna lettura effettuata, ho atteso troppo tempo
      lcd.print("Time out error");
    break;

    default: //qualsiasi altro valore di ritorno definisce un errore diverso
      lcd.print("Unknown error");
    break;
  }
}

 

Le prime problematiche non son mancate giustamente. 

Prima cosa il timer di Arduino non è precisissimo, quindi il millis() non contava bene. Questo è stato risolto inserendo un modulo RTC che mantiene la scadenza del tempo in modo migliore senza avere la limitazione di conteggio di un mese poco più del contatore millis(). Nella pagina successiva è spiegato il modulo RTC.

Ho notato che inviare stringhe troppo lunghe con il POST creava problemi per cui ho cercato di ridurre all'osso i vari pezzettini di testo da inviare.

Per il resto finalmente ho iniziato a caricare in automatico.

Altra problematica incontrata è che dopo qualche giorno di funzionamento non inviava più dati e l'unica cosa da fare per ovviare al problema era di staccare e riattaccare l'alimentazione. Però farlo manualmente non è la cosa migliore per cui ho inserito un relè al lato alimentazione azionato da un'uscita digitale che in automatico mi resetta la scheda. 

Questo ho capito molto tempo dopo che si trattava di errori in fase di compilatura da parte del compilatore di arduino. Con la versione 1.5.6-r2 per mac non ho più riscontrato questo problema e quindi il relè l'ho successivamente tolto.

Adesso non resta che spiegare come funziona modulo RTC e relè.