In questa guida vedremo come scrivere il codice per la realizzazione di una stazione meteo composta dai seguenti componenti:
– microcontrollore ESP32;
– sensore BME280 (temperatura, umidità, pressione);
– fotoresistenza (luminosità);
– anemometro ALMOT 08880905 (velocità vento);
– pluviometro MS-WH-SP-RG (precipitazioni).
Al termine dell’articolo sarà possibile scaricare il file con il codice completo.
Il progetto prevede il campionamento dei dati provenienti dai sensori ogni 60 secondi: ogni 10 rilevamenti, quindi ogni 10 minuti, la scheda calcola le medie ed invia i dati al database mysql utilizzando una pagina web.
Iniziamo includendo le librerie necessarie:
#include <Adafruit_Sensor.h> //per gestire il sensore BME280
#include <Adafruit_BME280.h> //per gestire il sensore BME280
#include <WiFiClientSecure.h> //per gestire la connessione alla rete Wifi e al client
#include <WiFi.h> //per gestire la connessione wifi
#include <Wire.h> //per gestire I2C
Come indicato dai commenti a lato le librerie servono per gestire il sensore BME280, per effettuare la connessione alla rete wifi ed inviare i dati mediante la pagina web oltre a gestire la comunicazione I2C.
Come primo passo dichiariamo le informazioni necessarie per collegarsi alla rete wifi e al client per la trasmissione dei dati:
//dati per la connessione alla rete Wifi
const char* SSID = "nome_rete_wifi";
const char* PASSWORD = "password_rete_wifi";
//dati per la connessione al client
const char* server = "www.nome_sito.it";
const uint16_t port = 443;
Procediamo quindi a dichiarare tutte le variabili utili per il settaggio dei vari sensori:
//dati intervallo lettura sensori
unsigned long tempo_start_lettura_sensori;
unsigned long intervallo_lettura_sensori = 60000; //effettuiamo una lettura dei sensori ogni 60 secondi
//dati sensore BME280
Adafruit_BME280 sensore_temp_umid_press_BME280;
//dati sensore fotoresistenza
const int pinFotoresistenza = 32;
//dati anemometro
const float pigreco = 3.141593;
const float raggio = 0.06; //raggio dell'anemometro in metri
const int pinAnemometro = 27;
int stato_reed_anemometro = 0;
int stato_reed_old_anemometro = 0; //variabile per evitare doppie pulsazioni
int num_pulsazioni = 0; //numero di pulsazioni (una per giro, c'è un solo magnete)
unsigned long int tempo_massimo_anemometro = 2000; //tempo dopo il quale calcolare la velocità e azzerare il numero di pulsazioni
//così non si conteggiano brevi folate di vento
unsigned long int inizio_elaborazione_anemometro;
float velocita_vento; //velocita vento rilevata dalla lettura
float velocita_vento_max = 0; //velocita vento massima misurata nelle 10 rilevazioni
float velocita_vento_min = 1000; //velocita vento minima misurata nelle 10 rilevazioni
//dati pluviometro
const float mmBasculata = 0.40; //millimetri per ogni basculata
const int pinPluviometro = 13;
int basculate = 0; //conteggio numero basculate
float mmPrecipitazioni = 0.0; //conteggio millimetri di pioggia
int stato_reed_pluviometro = 0;
int stato_reed_old_pluviometro = 0; //variabile per evitare doppie pulsazioni
float precipitazioni; //valore da trasmettere al database
Come già anticipato la stazione meteo eseguirà letture ogni 60 secondi ma invierà i dati al database solo al termine di 10 rilevamenti e dopo aver calcolato le relative medie. E’ quindi essenziale indicare anche tutte le variabili necessarie per effettuare queste operazioni:
//dichiarazione variabili ed array
int num_rilevazioni = 0;
float temperatura_media;
float umidita_media;
float pressione_media;
float luminosita_media;
float velocita_vento_media;
float temperatura_tot;
float umidita_tot;
float pressione_tot;
float luminosita_tot;
float velocita_vento_tot;
Come avrete sicuramente notato non abbiamo dichiarato la variabile relativa alle precipitazioni medie in quanto il valore inerente alla pioggia è un dato cumulativo nel tempo e non un valore medio.
Prima di passare alla parte di codice veramente interessante è bene tenere presente che queste tipologie di schede potrebbero bloccarsi o andare in errore non eseguendo più quanto da noi progettato. Per evitare che il sistema rimanga bloccato troppo a lungo effettueremo delle verifiche tali da riavviare in automatico la scheda ESP32. Questo avverrà nel caso in cui dopo il tempo di 12 letture non sia stato effettuato ancora un invio dei dati (ricordiamo che l’invio deve avvenire ogni 10 letture).
A tale scopo andiamo a dichiarare delle variabili per tenere monitorati diversi timer:
//timer per la verifica del corretto invio dei dati
uint32_t tempo_ultimo_invio_dati;
uint32_t tempo_timeout_restart_esp32 = 12 * intervallo_lettura_sensori; //dopo il tempo di 12 letture senza invio resetta la scheda
E’ arrivato finalmente il momento di dedicarci alla parte di codice void setup() ovvero al codice che viene processato solo una volta quando la scheda viene accesa ed alimentata.
Come prima operazione eseguiamo la connessione alla rete wifi (il sistema tenterà di connettersi alla rete ogni 500 millisecondi fino alla buona riuscita dell’operazione).
Successivamente assegniamo alla variabile tempo_start_lettura_sensori il valore di millis(): questa funzione restituisce il numero di millisecondi trascorsi dall’avvio del programma corrente sulla scheda e ci consente di capire ogni quanto leggere i sensori ed inviare i dati al database.
A seguire inizializziamo la comunicazione I2C ed il sensore BME280 e configuriamo i pin di anemometro e pluviometro come ingressi.
Infine assegniamo alla variabile tempo_ultimo_invio_dati il valore di millis(). Questa variabile è utile per memorizzare l’ultimo momento in cui abbiamo inviato i dati (in questo caso corrispondente all’avvio della scheda non avendo inviato ancora nessun valore).
Di seguito il codice che abbiamo appena descritto:
void setup() {
delay(500);
//connessione Wifi
WiFi.begin(SSID, PASSWORD);
while (WiFi.status() != WL_CONNECTED) {
delay(500);
}
//impostazione avvio millis per lettura sensori
tempo_start_lettura_sensori = millis();
//inizializzazione I2C
Wire.begin();
//inizializzazione sensore BME280
sensore_temp_umid_press_BME280.begin(0x76);
//impostazione pin anemometro
pinMode(pinAnemometro, INPUT);
//impostazione pin pluviometro
pinMode(pinPluviometro, INPUT);
//avvio timer per la verifica del corretto invio dei dati
tempo_ultimo_invio_dati = millis();
}
Ci dedichiamo ora al cuore pulsante del programma: la sezione void loop().
Questa parte viene eseguita continuamente dalla scheda fino al suo spegnimento.
Tutto il codice che tratteremo d’ora in avanti è quindi contenuto all’interno della sezione loo():
void loop() {
}
Come già anticipato queste tipologie di schede, seppur raramente, potrebbero bloccarsi.
Come prima cosa quindi andiamo continuamente a verificare se dall’ultimo invio dei dati è già trascorso il tempo massimo impostato dopo il quale viene effettuato un riavvio forzato della scheda:
//riavvio di ESP32 nel caso del non corretto invio dei dati
if (millis() - tempo_ultimo_invio_dati > tempo_timeout_restart_esp32) {
ESP.restart();
}
Trattiamo ora un argomento delicato: millis() è una funzione predefinita che restituisce i millisecondi trascorsi dall’avvio della scheda.
Questa funzione si basa su un registro a 32 bit che può contenere un valore massimo pari a 4.294.967.295.
Questo numero può sembrare grande, ma qualora la scheda dovesse rimanere sempre accesa, dopo un po’ di tempo questo registro raggiungerà il massimo valore che può contenere e poi andrà in overflow. Dopo 49,7 giorni quindi il registro ripartirà da zero!
Per risolvere questo problema basterà scrivere la condizione nell’ordine seguente:
MILLIS – TEMPO_PRECEDENTE > INTERVALLO
Per i più curiosi che volessero approfondire il problema dell’overflow di millis() consiglio di guardare questo interessante video di Paolo Aliverti.
E’ finalmente arrivato il momento di dedicarci al primo sensore: il pluviometro!
//pluviometro
stato_reed_pluviometro = digitalRead(pinPluviometro); //legge il contatto reed
if (stato_reed_pluviometro != stato_reed_old_pluviometro) { //verifica se è cambiato lo stato
stato_reed_old_pluviometro = stato_reed_pluviometro; //se SI aggiorna lo stato
if (stato_reed_pluviometro == HIGH) { //controlla se lo stato è alto (passaggio magnete)
basculate++; //incrementa il numero di basculate
mmPrecipitazioni = mmPrecipitazioni + mmBasculata; //sommatoria millimetri di pioggia rilevati
}
}
Il principio di funzionamento di un pluviometro a vaschetta basculante è semplice: a seconda della quantità di pioggia il meccanismo della bascula si riempie e si svuota. Ogni movimento della bascula aziona un contatto reed generando un impulso al quale corrisponde una quantità sempre fissa di precipitazioni.
Il codice da noi creato verifica se lo stato di questo contatto cambia e in caso va ad aggiungere alla variabile mmPrecipitazioni la quantità di precipitazioni corrispondente ad un movimento (nel caso del nostro pluviometro 0.4mm).
A differenza delle altre grandezze fisiche gestite dalla stazione meteo, le precipitazioni rilevate nelle 10 letture vengono cumulate e non viene generata la media (inviamo al database la quantità di pioggia rilevata in totale nei 10 minuti di rilevamento).
Questa lettura avviene in continuo in quanto la bascula potrebbe scattare in qualsiasi momento a seconda dell’andamento della pioggia.
Per leggere invece i dati degli altri sensori dobbiamo attendere che siano trascorsi i 60 secondi che ci dividono da una misurazione e l’altra.
Per farlo andremo a verificare la condizione indicata di seguito e tutto il codice che andremo a vedere da qui in avanti sarà contenuto in questa sezione:
//verifica se è trascorso l'intervallo per la lettura dei sensori
if ((millis() - tempo_start_lettura_sensori) > intervallo_lettura_sensori) {
}
Una volta trascorso il minuto di attesa la prima cosa da fare è resettare la variabile tempo_start_lettura_sensori assegnandogli il nuovo tempo attuale di millis() così da poter calcolare i prossimi 60 secondi per la successiva lettura:
tempo_start_lettura_sensori = millis();
Ci dedichiamo ora all’anemometro il cui principio di funzionamento è simile al pluviometro: ad ogni giro un magnete aziona un contatto reed generando un impulso. Contando il numero di giri in un intervallo di tempo è possibile, con qualche formula, calcolare la velocità del vento.
Nel codice seguente andiamo proprio a verificare il numero di pulsazioni registrate in un intervallo di tempo prestabilito (nel nostro caso tempo_massimo_anemometro corrisponde a 2 secondi così da evitare di registrare eventuali folate di vento):
//anemometro
inizio_elaborazione_anemometro = millis(); //memorizza l'inizio dell'elaborazione
while (millis() - inizio_elaborazione_anemometro < tempo_massimo_anemometro) { //esegue fino a quando il tempo trascorso è minore al tempo massimo impostato
stato_reed_anemometro = digitalRead(pinAnemometro); //legge il contatto reed
if (stato_reed_anemometro != stato_reed_old_anemometro) { //verifica se è cambiato lo stato
stato_reed_old_anemometro = stato_reed_anemometro; //se SI aggiorna lo stato
if (stato_reed_anemometro == HIGH) { //controlla se lo stato è alto (passaggio magnete)
num_pulsazioni = num_pulsazioni + 1; //aggiorna il contatore delle pulsazioni
}
}
delay(10);
//reset di inizio_elaborazione_anemometro in caso di overflow di millis che si azzera
if (millis() - inizio_elaborazione_anemometro < 0) {
inizio_elaborazione_anemometro = millis();
}
}
Una volta contate le pulsazioni registrate in 2 secondi dobbiamo ora calcolare la velocità del vento:
float tempo_trascorso_anemometro_in_secondi = (tempo_massimo_anemometro / 1000.0); //converte in secondi
velocita_vento = ((num_pulsazioni / tempo_trascorso_anemometro_in_secondi) * 5 / 2) * 2.5;
velocita_vento_tot = velocita_vento_tot + velocita_vento;
//calcolo velocita_vento_max
if (velocita_vento >= velocita_vento_max) {
velocita_vento_max = velocita_vento;
}
//calcolo velocita_vento_min
if (velocita_vento <= velocita_vento_min) {
velocita_vento_min = velocita_vento;
}
num_pulsazioni = 0; //azzera il contatore delle pulsazioni per una nuova lettura
L’anemometro utilizzato ha dimostrato da alcuni piccoli test di rilevare per ogni 2 impulsi/sec una velocità del vento di circa 5Km/h.
Aggiungiamo alla rilevazione anche un coefficiente di correzione pari a 2.5 ed otteniamo la seguente formula:
velocita_vento = ((num_pulsazioni / tempo_trascorso_anemometro_in_secondi) * 5 / 2) * 2.5
Passiamo ora al sensore BME280 che ci consente di rilevare i valori di temperatura, umidità e pressione eseguendo il codice seguente:
//sensore BME280
temperatura_tot = temperatura_tot + sensore_temp_umid_press_BME280.readTemperature();
umidita_tot = umidita_tot + sensore_temp_umid_press_BME280.readHumidity();
pressione_tot = pressione_tot + sensore_temp_umid_press_BME280.readPressure();
Infine non ci resta che dedicarci all’acquisizione dei dati provenienti dalla fotoresistenza inerenti alle condizioni di luminosità:
//sensore fotoresistenza
luminosita_tot = luminosita_tot + analogRead(pinFotoresistenza);
Una volta rilevati tutti i valori il sistema si prepara per rieseguire le stesse operazioni ogni 60 secondi fino a quando non si raggiunge la decima rilevazione: dopo 10 minuti infatti, ovvero dopo aver raccolto 10 valori per ogni sensore, il sistema provvederà a calcolare la media dei valori raccolti ed invierà i dati al database mysql.
Tutto questo viene svolto dal codice seguente che ora andiamo ad analizzare assieme passo per passo:
//operazioni da eseguire alla decima lettura eseguita
if (num_rilevazioni == 9) {
//calcolo dei valori da trasmettere al database
temperatura_media = temperatura_tot / 10.00;
umidita_media = umidita_tot / 10.00;
pressione_media = pressione_tot / 10.00;
luminosita_media = luminosita_tot / 10.00;
velocita_vento_media = velocita_vento_tot / 10.00;
precipitazioni = mmPrecipitazioni;
//trasmissione dati al server
//collegamento all'host www.nome_sito.it
WiFiClientSecure client;
client.setInsecure(); //non si utilizzano certificati di sicurezza
if (client.connect(server, port)) {
//verifica che il collegamento col client sia andato a buon fine
client.print("GET /aggiungi.php?");
client.print("temp=");
client.print(temperatura_media);
client.print("&umid=");
client.print(umidita_media);
client.print("&pres=");
client.print(pressione_media);
client.print("&lumi=");
client.print(luminosita_media);
client.print("&velo=");
client.print(velocita_vento_media);
client.print("&velo_max=");
client.print(velocita_vento_max);
client.print("&velo_min=");
client.print(velocita_vento_min);
client.print("&prec=");
client.print(precipitazioni);
client.println(" HTTP/1.1");
client.println("Host: www.nome_sito.it");
client.println("Access-Control-Allow-Origin: *");
client.println("Connection: close");
client.println();
delay(500); //delay fondamentale altrimenti a volte chiude la connessione prima che la pagina sia stata correttamente interrogata
client.stop();
delay(500);
tempo_ultimo_invio_dati = millis(); //aggiornamento del timer per la verifica del corretto invio dei dati
}
//azzeramento delle variabili
num_rilevazioni = -1; //appena fuori dal if ci sarà subito un +1 che la porta al valore iniziale 0
temperatura_tot = 0;
umidita_tot = 0;
pressione_tot = 0;
luminosita_tot = 0;
velocita_vento_tot = 0;
velocita_vento_max = 0;
velocita_vento_min = 1000;
precipitazioni = 0; //valore da trasmettere al database
basculate = 0;
mmPrecipitazioni = 0;
}
num_rilevazioni = num_rilevazioni + 1;
In prima battuta andiamo a calcolare le medie delle 10 rilevazioni per i valori di temperatura, umidità, pressione, luminosità e velocità del vento. Il valore delle precipitazioni invece è dato dalla sommatoria delle 10 misurazioni (valore cumulativo nei 10 minuti trascorsi).
Ora che tutti i dati sono pronti il sistema invia i valori al database mysql tramite la pagina web aggiungi.php ed utilizzando il metodo GET, ovvero indicando i dati all’interno del link per ogni singola variabile.
A questo punto aggiorniamo la variabile tempo_ultimo_invio_dati = millis() per la verifica del corretto invio dei dati ed azzeriamo tutte le variabili per i conteggi ed i contatori.
Il file contenente il codice completo è disponibile cliccando qui ma ricordate di modificare i valori relativi alla vostra rete wifi ed al vostro sito web che ospita la pagina aggiungi.php.
