La nostra centralina “meteo” con sensori di gas nocivi può catturare in tempo reale la presenza di +25 sostanze tossiche, tra cui idrocarburi e ossidi di azoto e visualizzare la concentrazione dei gas, la temperatura e l’umidità dell’aria su un qualunque dispositivo dotato di browser Web.
Il progetto usa stazioni trasmittenti multiple, da collocare in zone anche distanti e non coperte dal segnale Wi-Fi: Sfruttando il protocollo ESP-NOW di Espressif la centralina può visualizzare i dati dei sensori posti fino a 800 metri di distanza!
Per la stazione trasmittente abbiamo inoltre selezionato dei componenti di pregio, come i due sensori di gas MQ2 e MQ135. Questi dispositivi garantiscono delle misurazioni affidabili ad un un costo contenuto, ed essendo dotati di connettori con passo di 2.54 mm permettono di assemblare tutto il prototipo su una classica breadboard da 830 punti.
Il progetto è facilmente estensibile per leggere il valore di otto diversi trasmettitori con minime modifiche ai programmi. A tale scopo tutto il software viene distribuito in modalità “Open Source” e quindi completamente gratuito e personalizzabile.
Con il dispositivo potresti, ad esempio, controllare la qualità dell’aria nella tua casa e monitorare gas come CO, metano, GPL e fumi di combustione. In questo modo otterresti un ambiente più sicuro in tutti locali compresi box e garage esterni. Inoltre il sensore MQ2 potrebbe diventare un alleato prezioso per anticipare problemi all’impianto del metano, a stufe e scaldabagni a gas.
La centralina può sicuramente aiutarti a prevenire malanni legati agli sbalzi di temperatura e definire una qualità dell’aria superiore grazie al sensore incorporato MQ135. Il sensore infatti riesce a tracciare la infiltrazione di molti inquinanti industriali, come il benzene e gli ossidi di azoto e i dannosi “vapori” di ammoniaca e trielina.
All’aperto la centralina può controllare la qualità dell’aria in giardini, parchi e camping grazie ai due sensori MQ. Avrai solo bisogno di una sorgente di alimentazione a 5V con attacco USB, una esigenza che puoi assolvere facilmente con degli economici power bank per telefonia mobile.
Per quanto riguarda i dati e il server Web, la centralina funziona egregiamente sfruttando il solo hotspot del telefonino e con un consumo di dati molto ridotto grazie alla tecnologia di programmazione “AJAX”.
Nell’ambito della domotica potresti integrare la centralina nel tuo sistema domestico, per offrire anche il controllo completo dell’aria e dei gas pericolosi.
Nel giardinaggio potresti monitorare a basso costo le condizioni climatiche delle tue piante direttamente sul terreno e lontano dalla rete Wi-Fi.
E nel campo della industria e limitatamente alla qualità dell’aria, il dispositivo potrebbe controllare la conformità delle aziende alle normative ambientali.
Abbiamo scelto ESP32 per la sua formidabile connettività: la rete ESP-NOW, disponibile solo su questo controller, permette di porre i sensori ad oltre 800 metri dalla stazione ricevente: Una prestazione impossibile da ottenere con il solo Arduino e la normale copertura del Wi-Fi.
Nelle versioni future della centralina useremo gli stessi sensori e le schede di comunicazione dati “LoRa” per consentire la trasmissione fino a 2/3 chilometri in ambiente urbano e 10/15 chilometri in aria libera.
Pe realizzare il trasmettitore ti serviranno questi materiali:
Per costruire il trasmettitore puoi usare i connettori Dupont seguendo lo schema elettrico che vedi in basso. Ti suggerisco di inserire innanzitutto la scheda ESP32 e quindi i connettori per i sensori e l’alimentazione. Solo “dopo” dovresti inserire i sensori con il vantaggio di avere la filatura già pronta.
Per montare il trasmettitore non serve alcuna saldatura a meno che tu non voglia creare un prodotto molto robusto da distribuire commercialmente: Anche in questo caso, comunque potresti ridurre al minimo le saldature utilizzando la scheda multifunzione disponibile nel nostro ecommerce.
Per la compilazione di questo progetto puoi usare Arduino Ide o il compilatore a linea di Comando PlatformIO. Esiste una terza possibilità per compilare i programmi e cioè usare PlatformIO integrato in Visual Studio Code; ma per il momento ti forniremo istruzioni dettagliate solo per le prime due opzioni.
Per ottenere il codice sorgente specifico per il trasmettitore ti basta lanciare il comando GIT seguito dall’indirizzo del repository “corso-ESP32-centralina-meteo-trasmettitore” preparato per il nostro corso on-line. Puoi fare copia e incolla dagli esempio in basso, modificando se vuoi il nome della directory.
md c:\Progetti_Arduino
cd c:\Progetti_Arduino
git clone git@github.com:sebadima/corso-ESP32-centralina-meteo-trasmettitore.git
cd
mkdir Progetti_Arduino
cd Progetti_Arduino
git clone git@github.com:sebadima/corso-ESP32-centralina-meteo-trasmettitore.git
Fatto questo puoi aprire il programma con: “File”-> “Apri” dall’IDE e rispondere alla eventuale richiesta di spostare la directory o il “file main.ino”. Potresti teoricamente compilare subito il programma, ma otterresti solo degli errori relativi alle librerie mancanti. Ad esempio potrebbero mancare due librerie come la “esp_now” o la “DHT” dedicata al sensore DHT11.
Detto ciò vediamo come risolvere il problema delle librerie mancanti…
Per installare le librerie mancanti puoi procedere in questo modo:
Ad esempio per installare la libreria del DHT11 puoi eseguire gli stessi passi digitando: “DHT”:
Vedrai sulla sinistra un elenco delle librerie possibili e nel nostro caso puoi scegliere la libreria “DHT Sensor Lybrary” di Adafruit nella versione 1.4.6.
Clicca su “INSTALL” e potrai rilanciare la compilazione dello sketch. Purtroppo dovrai eseguire questi passaggi per ogni libreria mancante fino a quando il programma verrà compilato correttamente. Dopo di ciò potrai fare l’upload sulla ESP32 cliccando su “Sketch”->“Upload”.
La compilazione con Platformio è molto più diretta perchè questo software provvede a installare le librerie leggendo il file “platformio.ini” che abbiamo inserito su Github. Per compilare puoi procedere semplicemente facendo copia e incolla dei comandi sottostanti:
git clone git@github.com:sebadima/corso-ESP32-centralina-meteo-trasmettitore.git
cd corso-ESP32-centralina-meteo-trasmettitore
make upload
platformio device monitor --baud 115200 --rts 0 --dtr 0 --port /dev/ttyUSB0
Dopo la compilazione il comando “platformio device monitor” provvede a lanciare il monitor seriale sulla porta ttyUSB0. Se questo valore non dovesse corrispondere con la porta del tuo sistema Linux o Windows dovresti rilanciare la ultima riga con la porta realmente in uso.
1#include <Arduino.h>
2#include <esp_now.h>
3#include <WiFi.h>
4#include <esp_wifi.h>
5#include "DHT.h"
6#include "soc/soc.h"
7#include "soc/rtc_cntl_reg.h"
8
9constexpr char WIFI_SSID[] = "SSID-da-modificare";
10
11// Indirizzi MAC dei dispositivi di destinazione
12// trovati con la utility apposita
13// indirizzo MAC di destinazione: A0:A3:B3:97:83:E8
14constexpr uint8_t ESP_NOW_RECEIVER[] = { 0xA0, 0xA3, 0xB3, 0x97, 0x83, 0xE8 };
15
16// Struct per definire il formato dei dati
17typedef struct struct_messaggio {
18 char a[32];
19 int umidita;
20 float temperatura;
21 float gas_1;
22 float gas_2;
23 int contatore;
24} struct_messaggio;
25
26struct_messaggio Dati;
27esp_now_peer_info_t peerInfo;
28
29#define DHTPIN 13
30#define DHTTYPE DHT11
31
32DHT dht(DHTPIN, DHTTYPE);
33float t, h, g_1, g_2;
34int lost_packages;
35int ix;
36int Gas_1 = 33;
37int Gas_2 = 35;
38
39#define DELAY_RECONNECT 600 // intervallo in secondi per forzare il reboot
40volatile int interruptCounter;
41int totalInterruptCounter;
42hw_timer_t * timer = NULL;
43portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
44
45
46
47void IRAM_ATTR onTimer()
48{
49 // https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Timer/RepeatTimer/RepeatTimer.ino
50 portENTER_CRITICAL_ISR(&timerMux);
51 interruptCounter++;
52 if (lost_packages >=15) {
53 ESP.restart(); // Riesegui la connessione al nuovo canale WIFI
54 }
55 portEXIT_CRITICAL_ISR(&timerMux);
56}
57
58
59int32_t getWiFiChannel(const char *ssid) {
60
61 if (int32_t n = WiFi.scanNetworks()) {
62 for (uint8_t i=0; i<n; i++) {
63 if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
64 return WiFi.channel(i);
65 }
66 }
67 }
68
69 return 0;
70}
71
72
73void initWiFi() {
74
75 WiFi.mode(WIFI_MODE_STA);
76
77 // acquisice il canale usato dalla WIFI
78 int32_t channel = getWiFiChannel(WIFI_SSID);
79
80 esp_wifi_set_promiscuous(true);
81 esp_wifi_set_channel(channel, WIFI_SECOND_CHAN_NONE);
82 esp_wifi_set_promiscuous(false);
83
84 Serial.printf("SSID: %s\n", WIFI_SSID);
85 Serial.printf("Channel: %u\n", WiFi.channel());
86}
87
88
89void initEspNow() {
90
91 if (esp_now_init() != ESP_OK) {
92 Serial.println("ESP NOW failed to initialize");
93 while (1);
94 }
95
96 memcpy(peerInfo.peer_addr, ESP_NOW_RECEIVER, 6);
97 peerInfo.ifidx = WIFI_IF_STA;
98 peerInfo.encrypt = false;
99
100 if (esp_now_add_peer(&peerInfo) != ESP_OK) {
101 Serial.println("ESP NOW pairing failure");
102 while (1);
103 }
104}
105
106
107void suInvioDati(const uint8_t *mac_addr, esp_now_send_status_t status) {
108 Serial.print("\r\nStatus invio:\t");
109 Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Consegna positiva" : "Errore di consegna");
110 if (status != ESP_NOW_SEND_SUCCESS) {
111 lost_packages ++;
112 }
113 if (lost_packages >=15) {
114 Serial.println("ESP restarting on lost packages");
115 ESP.restart(); // Riesegui la connessione al nuovo canale WIFI
116 }
117}
118
119
120void setup() {
121 Serial.begin(115200);
122 WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disable brownout detector
123
124 initWiFi();
125 initEspNow();
126
127 timer = timerBegin(0, 80, true);
128 timerAttachInterrupt(timer, &onTimer, true);
129 timerAlarmWrite(timer, DELAY_RECONNECT * 1000000, true);
130 timerAlarmEnable(timer);
131
132 dht.begin();
133 pinMode(Gas_1, INPUT);
134 pinMode(Gas_2, INPUT);
135
136 esp_now_register_send_cb(suInvioDati);
137 ix = 1;
138}
139
140
141void loop() {
142
143 h = dht.readHumidity();
144 t = dht.readTemperature();
145 g_1 = analogRead(Gas_1);
146 g_2 = analogRead(Gas_2);
147
148 if (isnan(g_1) )
149 {
150 Serial.println(F("Non riesco a leggere dal sensore di GAS 1!"));
151 return;
152 }
153
154 if (isnan(g_2) )
155 {
156 Serial.println(F("Non riesco a leggere dal sensore di GAS 2!"));
157 return;
158 }
159
160 if (isnan(t) )
161 {
162 Serial.println(F("Non riesco a leggere dal sensore DHT!"));
163 return;
164 }
165
166 Serial.print("Temperatura: ");
167 Serial.println(t);
168 Serial.print("Umidità: ");
169 Serial.println(h);
170 Serial.print("Gas_1: ");
171 Serial.println(g_1);
172 Serial.print("Gas_2: ");
173 Serial.println(g_2);
174
175 strcpy(Dati.a, "Rilevazioni DHT11");
176 Dati.umidita = (int) h;
177 Dati.temperatura = t;
178 Dati.gas_1 = g_1;
179 Dati.gas_2 = g_2;
180 Dati.contatore = ix;
181
182 // invio del messaggio a ESP1
183 esp_err_t result = esp_now_send(0, (uint8_t *) &Dati, sizeof(Dati));
184
185 if (result == ESP_OK) {
186 Serial.println("Messaggio inviato con successo");
187 }
188 else {
189 Serial.println("Errore di invio");
190 }
191
192 ix = ix + 1;
193 delay(2000);
194}
Per usare il programma con la tua rete Wi-Fi o hotspot devi modificare la riga #9:
constexpr char WIFI_SSID[] = "SSID-da-modificare";
e inserire il SSID (il nome) della tua rete fissa o mobile.
Per funzionare la rete ESP-NOW pretende di sapere l’indirizzo MAC univoco della scheda ESP32 di destinazione.
Un indirizzo MAC (Media Access Control) è un identificativo univoco assegnato a ogni scheda di rete (NIC) presente in un dispositivo informatico. È un numero di 12 cifre esadecimali, solitamente rappresentato in gruppi di due coppie separate da due punti (ad esempio, 00:11:22:33:44:55).
// indirizzo MAC di destinazione: A0:A3:B3:97:83:E8
constexpr uint8_t ESP_NOW_RECEIVER[] = { 0xA0, 0xA3, 0xB3, 0x97, 0x83, 0xE8 };
Per ottenere il valore MAC della scheda abbiamo usato il programma descritto nella sezione #7.2 del nostro corso e quindi ti rimandiamo alle istruzioni lì pubblicate. Dopo avere ottenuto l’indirizzo MAC della tua scheda dovrai ovviamente inserirlo nel programma mantendendo la forma di scrittura 0x00.
// Struct per definire il formato dei dati
typedef struct struct_messaggio {
char a[32];
int umidita;
float temperatura;
float gas_1;
float gas_2;
int contatore;
} struct_messaggio;
I dati dei sensori non vengono comunicati separatamente ma sono raggruppati in una struct del linguaggio C++. La struct è un costrutto sintattico che si limita a definire soltanto il “typedef” (il formato) senza realmente creare spazio nella zona variabili della RAM.
La istruzione successiva e cioè “struct_messaggio Dati;” crea effettivamente uno spazio nella RAM del controller e gli assegna il valore prescelto: Nel nostro caso semplicemente “Dati”, che useremo per gestire e trasmettere le letture dei sensori e il contatore numerico.
La prossima istruzione (contenuta all’interno della funzione loop) utilizza le variabili prelevandole con il puntatore “&Dati” e li fornisce alla funzione “esp_now_send()”.
Il programma utilizza delle funzioni avanzate di ESP32 per resettare la scheda dopo 15 pacchetti dati persi. Come in ogni applicazione IoT non possiamo pensare di stare al computer per monitorare il comportamento dei dispositivi e dobbiamo prevedere delle istruzione di “recupero” automatico della connessione in caso di problemi.
I controller ESP32 sono dotati di 4 timer hardware, ognuno dei quali è un contatore up/down a 64 bit generico con un prescaler a 16 bit. Fa eccezione la scheda ESP 32C3 che ha solo 2 timer ognuno dei quali è invece di 54 bit. I timer di ESP32 funzionano in modalità roll e alla fine del conteggio ad esempio 800000 ripartono da zero.
#define DELAY_RECONNECT 600
// intervallo in secondi per forzare il reboot
volatile int interruptCounter;
int totalInterruptCounter;
hw_timer_t * timer = NULL;
portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
void IRAM_ATTR onTimer()
{
portENTER_CRITICAL_ISR(&timerMux);
interruptCounter++;
if (lost_packages >=15) {
ESP.restart(); // Riesegui la connessione al nuovo canale WIFI
}
portEXIT_CRITICAL_ISR(&timerMux);
}
La configurazione di interrupt viene completata dentro la funzione “setup()”
timer = timerBegin(0, 80, true);
timerAttachInterrupt(timer, &onTimer, true);
timerAlarmWrite(timer, DELAY_RECONNECT * 1000000, true);
timerAlarmEnable(timer);
Le prime cinque righe impostano la struttura dati suggerita da Espressif per la gestione degli interrupt mentre la successiva funzione “onTimer()” viene richiamata automaticamente dal sistema.
Il ricevitore della centraline è collegato alla rete Wi-Fi per fornire in HTML i dati dei sensori, e la necessità di fare convivere ESP-NOW e Wi-Fi impone che il dure operino nello stesso canale. Con il pezzo di programma sotto il trasmettitore legge il nome della rete dal parametro passato alla funzione:
“getWiFiChannel” con il parametro: “(const char *ssid)” ed effettua una semplice scansione di tutti i canali.
Per determinare il numero real dei canali disponibili il programma usa la istruzione “int32_t n = WiFi.scanNetworks()” e quindi lancia un ciclo in loop con: “for (uint8_t i=0; i<n; i++)” dove “i<n;” serve a limitare il numero di ripetizioni. Se la istruzione “strcmp()” rileva il canale con il nome giusto ne ritorna il codice al resto del programma. La funzione “InitWiFi()” userà il codice ottenuto durante la fase di boot del controller.
int32_t getWiFiChannel(const char *ssid) {
if (int32_t n = WiFi.scanNetworks()) {
for (uint8_t i=0; i<n; i++) {
if (!strcmp(ssid, WiFi.SSID(i).c_str())) {
return WiFi.channel(i);
}
}
}
return 0;
}
Questa è forse la parte più importante el programma e usa la istruzione “if (lost_packages >=15)” per attivare la procedura di restart del controller e rilanciare la connessione al canale Wi-Fi esatto.
void suInvioDati(const uint8_t *mac_addr, esp_now_send_status_t status) {
Serial.print("\r\nStatus invio:\t");
Serial.println(status == ESP_NOW_SEND_SUCCESS ? "Consegna positiva" : "Errore di consegna");
if (status != ESP_NOW_SEND_SUCCESS) {
lost_packages ++;
}
if (lost_packages >=15) {
Serial.println("ESP restarting on lost packages");
ESP.restart(); // Riesegui la connessione al nuovo canale WIFI
}
}
Il ricevitore non necessita realmente di una fase di assemblaggio a parte la saldatura di una antenna esterna per ESP32 come vedi nella foto sotto, ma anche questa fase può essere evitata usando una ESP32CAM come ricevitore con la presa per antenna
Puoi usare Arduino Ide o il compilatore a linea di Comando PlatformIO. Noi in genere preferiamo Platformio ma ciò non significa che il programma non possa essere compilato con Arduino IDE o che il codice oggetto sia migliore: semplicemente preferiamo installare le librerie in automatico come riesce a fare comodamente PlatformIO.
Per scaricare il codice sorgente del ricevitore puoi andare nella linea di comando di Windows usando la PowerShell o nel terminale di Linux e digitare o fare copia e incolla di:
git clone git@github.com:sebadima/corso-ESP32-centralina-meteo_ricevitore.git
Fatto questo puoi aprire il programma con: “File”-> “Apri” dall’IDE e rispondere alla eventuale richiesta di spostare la directory o il “file main.ino”. Per installare le librerie mancanti. Per installare le librerie mancanti puoi procedere in questo modo:
Se non vuoi usare Github puoi fare copia e incolla del programma sottostante e procedere allo stesso modo:
1#include "ESPAsyncWebServer.h"
2#include <Arduino_JSON.h>
3#include <Arduino.h>
4#include <esp_now.h>
5#include <esp_wifi.h>
6#include <WiFi.h>
7#include "soc/soc.h"
8#include "soc/rtc_cntl_reg.h"
9#include "BluetoothSerial.h"
10
11constexpr char WIFI_SSID[] = "SSID-da-modificare";
12constexpr char WIFI_PASS[] = "PASSWORD-da-modificare";
13
14// Setta un indirizzo IP Fisso
15IPAddress local_IP(192, 168, 1, 200);
16// Setta l'indirizzo del Gateway
17IPAddress gateway(192, 168, 1, 1);
18IPAddress subnet(255, 255, 0, 0);
19IPAddress primaryDNS(8, 8, 8, 8); //opzionale
20IPAddress secondaryDNS(8, 8, 4, 4); //opzionale
21
22// Struttura dati, deve corrispondere a quella del mittente
23typedef struct struttura_dati {
24 char v0[32];
25 int v1;
26 float v2;
27 float v3;
28 float v4;
29 unsigned int progressivo;
30} struttura_dati;
31
32struttura_dati LettureSensori;
33
34#if !defined(CONFIG_BT_ENABLED) || !defined(CONFIG_BLUEDROID_ENABLED)
35#error Bluetooth is not enabled! Please run `make menuconfig` to and enable it
36#endif
37
38BluetoothSerial SerialBT;
39JSONVar board;
40AsyncWebServer server(80);
41AsyncEventSource events("/events");
42
43volatile int interruptCounter;
44int totalInterruptCounter;
45hw_timer_t * timer = NULL;
46portMUX_TYPE timerMux = portMUX_INITIALIZER_UNLOCKED;
47#define DELAY_RECONNECT 60
48
49
50
51void IRAM_ATTR onTimer()
52{
53 // https://github.com/espressif/arduino-esp32/blob/master/libraries/ESP32/examples/Timer/RepeatTimer/RepeatTimer.ino
54 portENTER_CRITICAL_ISR(&timerMux);
55 interruptCounter++;
56 if (WiFi.status() != WL_CONNECTED)
57 {
58 ESP.restart();
59 }
60 portEXIT_CRITICAL_ISR(&timerMux);
61}
62
63void suDatiRicevuti(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
64 // Copi l'indirizzo MAC del mittente
65 char macStr[18];
66 Serial.print("Pacchetto ricevuto da: ");
67 snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
68 mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
69 Serial.println(macStr);
70 memcpy(&LettureSensori, incomingData, sizeof(LettureSensori));
71
72 board["v1"] = LettureSensori.v1;
73 board["v2"] = LettureSensori.v2;
74 board["v3"] = LettureSensori.v3;
75 board["v4"] = LettureSensori.v4;
76 board["progressivo"] = String(LettureSensori.progressivo);
77 String jsonString = JSON.stringify(board);
78 events.send(jsonString.c_str(), "new_readings", millis());
79
80 Serial.printf("Board ID %u: %u bytes\n", LettureSensori.v1, len);
81 Serial.printf("t valore: %4.2f \n", LettureSensori.v2);
82 Serial.printf("h valore: %4.2f \n", LettureSensori.v3);
83 Serial.printf("Progressivo: %d \n", LettureSensori.progressivo);
84 Serial.println();
85}
86
87const char index_html[] PROGMEM = R"rawliteral(
88<!DOCTYPE HTML><html>
89<head>
90 <title>Robotdazero - rete "Ambientale" con ESP32</title>
91 <meta name="viewport" content="width=device-width, initial-scale=1">
92 <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.2/css/all.css" integrity="sha384-fnmOCqbTlWIlj8LyTjo7mOUStjsKC4pOpQbqyi7RrhN7udi9RwhKkMHpvLbHG9Sr" crossorigin="anonymous">
93 <link rel="icon" href="data:,">
94 <style>
95 html {font-family: Arial; display: inline-block; text-align: center;}
96 p { font-size: 1.2rem;}
97 body { margin: 0;}
98 .topnav { overflow: hidden; background-color: #2f4468; color: white; font-size: 1.7rem; }
99 .content { padding: 20px; }
100 .card { background-color: white; box-shadow: 2px 2px 12px 1px rgba(140,140,140,.5); }
101 .cards { max-width: 700px; margin: 0 auto; display: grid; grid-gap: 2rem; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
102 .reading { font-size: 2.8rem; }
103 .packet { color: #bebebe; }
104 .card.temperature { color: #fd7e14; }
105 .card.humidity { color: #1b78e2; }
106 </style>
107</head>
108<body>
109 <div class="topnav">
110 <h3>ROBOTDAZERO - rete "Ambientale" con ESP32</h3>
111 </div>
112 <div class="content">
113 <div class="cards">
114 <div class="card temperature">
115 <h4><i class="fas fa-thermometer-half"></i> SCHEDA #1 - TEMPERATURA</h4><p><span class="reading"><span id="t1"></span> °C</span></p><p class="packet">sensore DHT11: <span id="rt1"></span></p>
116 </div>
117 <div class="card humidity">
118 <h4><i class="fas fa-tint"></i> SCHEDA #1 - UMIDITA'</h4><p><span class="reading"><span id="h1"></span> %</span></p><p class="packet">sensore DHT11: <span id="rh1"></span></p>
119 </div>
120 <div class="card temperature">
121 <h4><i class="far fa-bell"></i> SCHEDA #1 - Fumo/Metano</h4><p><span class="reading"><span id="t2"></span> ppm</span></p><p class="packet">sensore MQ-2: <span id="rt2"></span></p>
122 </div>
123 <div class="card humidity">
124 <h4><i class="far fa-bell"></i> SCHEDA #1 - Qualita' dell'aria</h4><p><span class="reading"><span id="h2"></span> ppm</span></p><p class="packet">sensore MQ-135: <span id="rh2"></span></p>
125 </div>
126 </div>
127 </div>
128<script>
129if (!!window.EventSource) {
130 var source = new EventSource('/events');
131
132 source.addEventListener('open', function(e) {
133 console.log("Events Connected");
134 }, false);
135 source.addEventListener('error', function(e) {
136 if (e.target.readyState != EventSource.OPEN) {
137 console.log("Events Disconnected");
138 }
139 }, false);
140
141 source.addEventListener('message', function(e) {
142 console.log("message", e.data);
143 }, false);
144
145 source.addEventListener('new_readings', function(e) {
146 console.log("new_readings", e.data);
147 var obj = JSON.parse(e.data);
148 document.getElementById("t1").innerHTML = Math.round(obj.v2 * 100) / 100;
149 document.getElementById("h1").innerHTML = obj.v1;
150 document.getElementById("t2").innerHTML = obj.v3;
151 document.getElementById("h2").innerHTML = obj.v4;
152 }, false);
153}
154</script>
155</body>
156</html>)rawliteral";
157
158void initBT() {
159 SerialBT.begin("ESP32-sensori");
160 Serial.println("Dispositivo avviato, puoi accoppiarlo con bluetooth...");
161}
162
163void initWiFi() {
164 WiFi.mode(WIFI_MODE_APSTA);
165
166 if(!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
167 Serial.println("STA Failed to configure");
168 }
169
170 WiFi.begin(WIFI_SSID, WIFI_PASS);
171
172 Serial.printf("Connecting to %s .", WIFI_SSID);
173 while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(200); }
174 Serial.println("ok");
175
176 IPAddress ip = WiFi.localIP();
177
178 Serial.printf("SSID: %s\n", WIFI_SSID);
179 Serial.printf("Channel: %u\n", WiFi.channel());
180 Serial.printf("IP: %u.%u.%u.%u\n", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, ip >> 24);
181}
182
183void initEspNow() {
184 if (esp_now_init() != ESP_OK) {
185 Serial.println("ESP NOW failed to initialize");
186 while (1);
187 }
188 esp_now_register_recv_cb(suDatiRicevuti);
189}
190
191void setup() {
192 Serial.begin(115200);
193 WRITE_PERI_REG(RTC_CNTL_BROWN_OUT_REG, 0); //disabilita brownout detector
194
195 initWiFi();
196 initEspNow();
197 initBT();
198
199 timer = timerBegin(0, 80, true);
200 timerAttachInterrupt(timer, &onTimer, true);
201 timerAlarmWrite(timer, DELAY_RECONNECT * 1000000, true);
202 timerAlarmEnable(timer);
203
204 server.on("/", HTTP_GET, [](AsyncWebServerRequest *request){
205 request->send_P(200, "text/html", index_html);
206 });
207
208 events.onConnect([](AsyncEventSourceClient *client){
209 if(client->lastId()){
210 Serial.printf("Riconnessione! Ultmo messaggio ricevuto: %u\n", client->lastId());
211 }
212 client->send("hello!", NULL, millis(), 10000);
213 });
214 server.addHandler(&events);
215 server.begin();
216}
217
218void loop() {
219 static unsigned long lastEventTime = millis();
220 static const unsigned long EVENT_INTERVAL_MS = 5000;
221 if ((millis() - lastEventTime) > EVENT_INTERVAL_MS) {
222 events.send("ping",NULL,millis());
223 lastEventTime = millis();
224 }
225}
git clone git@github.com:sebadima/corso-ESP32-centralina-meteo_ricevitore.git
cd corso-ESP32-centralina-meteo-trasmettitore
make upload
platformio device monitor --baud 115200 --rts 0 --dtr 0 --port /dev/ttyUSB0
Poichè il ricevitore si collega effettivamente alla rete Wi-Fi, nelle righe successive dobbiamo impostare le variabili per la connessione. DNon cis sono particolarità da notare a parte la riga “IPAddress local_IP(192, 168, 1, 200);” che si servirà ad impostare l’IP fisso del server Web. Se preferisci puoi cambiarlo per evitare una collisione con altri dispositivi collegati.
constexpr char WIFI_SSID[] = "SSID-da-modificare";
constexpr char WIFI_PASS[] = "PASSWORD-da-modificare";
// Setta un indirizzo IP Fisso
IPAddress local_IP(192, 168, 1, 200);
// Setta indirizzo del Gateway
IPAddress gateway(192, 168, 1, 1);
IPAddress subnet(255, 255, 0, 0);
IPAddress primaryDNS(8, 8, 8, 8); //opzionale
IPAddress secondaryDNS(8, 8, 4, 4); //opzionale
Nella funzione “initWiFi()” la radio dell’ESP32 viene inizializzata in modalità mista con il comando: “WiFi.mode(WIFI_MODE_APSTA);” per permette l’uso simultaneo di ESP-NOW e Wi-Fi. La istruzione " Serial.printf(“Channel: %u\n”, WiFi.channel());" serve in modalità di debug per controllare il canale in cui avviene la connessione. E’ importante avere una idea del canale perchè alcuni router potrebbero essere configurati solo con il Wi-Fi a 5Ghz attivato e dare risultati imprevedibili.
void initWiFi() {
WiFi.mode(WIFI_MODE_APSTA);
if(!WiFi.config(local_IP, gateway, subnet, primaryDNS, secondaryDNS)) {
Serial.println("STA Failed to configure");
}
WiFi.begin(WIFI_SSID, WIFI_PASS);
Serial.printf("Connecting to %s .", WIFI_SSID);
while (WiFi.status() != WL_CONNECTED) { Serial.print("."); delay(200); }
Serial.println("ok");
IPAddress ip = WiFi.localIP();
Serial.printf("SSID: %s\n", WIFI_SSID);
Serial.printf("Channel: %u\n", WiFi.channel());
Serial.printf("IP: %u.%u.%u.%u\n", ip & 0xff, (ip >> 8) & 0xff, (ip >> 16) & 0xff, ip >> 24);
}
I dati ricevuti dal trasmettitore devono seguire necessariamente lo stesso formato pena errori imprevedibili o blocco completo della trasmissione. Se torni al sorgente del trasmettitore vedrai che formato e sequenza delle variabili sono le stesse, mentre teoricamente non è necessario che abbiano lo stesso identificativo.
// Struttura dati, deve corrispondere a quella del mittente
typedef struct struttura_dati {
char v0[32];
int v1;
float v2;
float v3;
float v4;
unsigned int progressivo;
} struttura_dati;
Dopo avere letto i dati da ESP-NOw dobbiamo usarli nel nostro server Web e quindi li importiamo nella variabile JSON board che abbiamo definito ad inizio programma con “JSONVar board;”. I valori v1,v2,v3,v4 verrano poi usati dal server con queste istruzioni: “document.getElementById(“t1”).innerHTML = Math.round(obj.v2 * 100) / 100;”.
Poichè si tratta di un argomento un poco complesso lo tratteremo in una sezione successiva. Un altro pezzo interessante è la print dell’indirizzo MAC del mittente ottenuta con:
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x"
snprintf è estremamente simile a sprintf: Dopo tutto, i nomi delle funzioni differiscono solo dal carattere ’n’! Questa è in realtà una convenzione abbastanza comune in C: la funzione con la ’n’ richiede un limite superiore nel nostro caso lo definiamo con “sizeof(macStr)”. In genere la versione’ n ’ delle funzioni è più sicura e meno suscettibile agli overflow del buffer.
void suDatiRicevuti(const uint8_t * mac_addr, const uint8_t *incomingData, int len) {
// Copia indirizzo MAC del mittente
char macStr[18];
Serial.print("Pacchetto ricevuto da: ");
snprintf(macStr, sizeof(macStr), "%02x:%02x:%02x:%02x:%02x:%02x",
mac_addr[0], mac_addr[1], mac_addr[2], mac_addr[3], mac_addr[4], mac_addr[5]);
Serial.println(macStr);
memcpy(&LettureSensori, incomingData, sizeof(LettureSensori));
board["v1"] = LettureSensori.v1;
board["v2"] = LettureSensori.v2;
board["v3"] = LettureSensori.v3;
board["v4"] = LettureSensori.v4;
board["progressivo"] = String(LettureSensori.progressivo);
String jsonString = JSON.stringify(board);
events.send(jsonString.c_str(), "new_readings", millis());
Serial.printf("Board ID %u: %u bytes\n", LettureSensori.v1, len);
Serial.printf("t valore: %4.2f \n", LettureSensori.v2);
Serial.printf("h valore: %4.2f \n", LettureSensori.v3);
Serial.printf("Progressivo: %d \n", LettureSensori.progressivo);
Serial.println();
}
In questa sezione è utile notare la funzione “esp_now_register_recv_cb(suDatiRicevuti);” che definisce un hook verso “suDatiRicevuti” che verrà invocata in maniera automatica (asincrona) ogni volta che la scheda riceve dei dati. E’ importante definire in maniere asincrona le routine di ricezione dati per evitare che la schede sprechi preziosi cicli di clock per controllare continuamente se sono arrivati dei dati.
La programmazione asincrona è una tecnica che consente al programma di avviare un’attività potenzialmente di lunga durata e di essere ancora in grado di rispondere ad altri eventi durante l’esecuzione di tale attività, piuttosto che dover attendere che tale attività sia terminata. Una volta che l’attività è terminata, il programma viene presentato con il risultato.
void initEspNow() {
if (esp_now_init() != ESP_OK) {
Serial.println("ESP NOW failed to initialize");
while (1);
}
esp_now_register_recv_cb(suDatiRicevuti);
}
Il server dopo la connessione ad ESP-NOW e alla rete Wi-Fi riesce a mostrare in tempo reale le letture dei sensori: HTML non è adatto a questo tipo di visualizzazione e deve essere necessariamente integrato con la tecnologia Ajax. Ma iniziamo per gradi e vediamo intanto come viene conservato nella ram il codice HTML:
const char index_html[] PROGMEM = R"rawliteral(
<!DOCTYPE HTML><html>
<head>
...
...
</head>
<body>
...
...
</body>
<script>
if (!!window.EventSource) {
var source = new EventSource('/events');
source.addEventListener('open', function(e) {
console.log("Events Connected");
}, false);
source.addEventListener('error', function(e) {
if (e.target.readyState != EventSource.OPEN) {
console.log("Events Disconnected");
}
}, false);
source.addEventListener('message', function(e) {
console.log("message", e.data);
}, false);
source.addEventListener('new_readings', function(e) {
console.log("new_readings", e.data);
var obj = JSON.parse(e.data);
document.getElementById("t1").innerHTML = Math.round(obj.v2 * 100) / 100;
document.getElementById("h1").innerHTML = obj.v1;
document.getElementById("t2").innerHTML = obj.v3;
document.getElementById("h2").innerHTML = obj.v4;
}, false);
}
</script>
Nelle sezioni precedenti abbiamo già parlato di come implementare un server Web e quindi in questo caso ci concentriamo soprattutto sulle novità e sul funzionamenti di AJAX.
La istruzione “var source = new EventSource(’/events’);” aggiunge un una routine asincrona che viene attivata dall’arrivo dei nuovi dati e lo segnale sul monitor seriale con “console.log(“new_readings”, e.data);” ma soprattutto provvede a modificare il documento HTML con la istruzione:
“document.getElementById(“h1”).innerHTML = obj.v1;”.
Le cause di un malfunzionamento possono essere molte, ma ricadono fondamentalmente in queste tre tipologie:
un errato collegamento dei connettori: Il diagramma che ti forniamo rappresenta fedelmente il progetto realizzato da Robotdazero. ma ciò non garantisce che alcune versioni commerciali del DHT11 non possano avere diverse disposizioni del connettore dati. Se i pin di alimentazione sembrano restare coerenti nelle varie versioni in commercio, il pin dati potrebbe essere collegato a uno qualsiasi dei due pin liberi. Il problema comunque facilmente risolvibile facendo un poco di attenzione e ricontrollando “a vista” i connettori. Per facilitare il lavori di controllo ti consigliamo di adottare sempre colori nero e rosso per la alimentazione e verde o giallo per il segnale dati, in tal modi capire se il pin dati e stato collegato correttamente diventa quasi banale.
un problema alla alimentazione fornita dalla USB: La tensione fornito dalla USB in condizioni ideali riesce ad erogare la minima corrente richiesta dall’ESP32 e dai sensori, parliamo di mezzo di 350mA al massimo, ma su alcuni piccoli laptop o desktop danneggiati anche tale carico potrebbe rappresentare un problema. Inoltre ricorda che gli HUB per USB non sono sempre trasparenti alla corrente e potrebbero assorbirne una parte per il loro funzionamento. Inoltre, nel caso peggiore, l’UHB potrebbe avere difficoltà a mantenere la tensione costante se troppi dispositivi assorbono corrente nello stesso momento.
un problema hardware: Ad esempio il sistema potrebbe non funzionare per la rottura di uno dei sensori, un connettore Dupont spezzato (magari solo all’interno), la sezione radio dell’ESP32 danneggiata perchè hai collegato due antenne “troppo” vicine, un piedino rotto dell’ESP32, una breadboard difettosa, un cavo USB difettoso (un caso molto comune).
In questo articolo, abbiamo esplorato le potenzialità dell’IoT per la casa e il lavoro, utilizzando un ESP32 con sensori di qualità dell’aria e gas pericolosi come esempio pratico.
A livello domestico, l’implementazione di un sistema di monitoraggio IoT può portare a una maggiore sicurezza e comfort. La capacità di monitorare la qualità dell’aria e la temperatura può aiutare a creare un ambiente più sano e confortevole per la propria famiglia. Inoltre, la rilevazione di gas pericolosi può fornire un avvertimento tempestivo in caso di emergenza.
In ambito lavorativo, l’IoT può migliorare l’efficienza e la produttività. I sensori possono essere utilizzati per monitorare le condizioni ambientali in un singolo ufficio o in molteplici locali, garantendo un ambiente di lavoro sicuro e confortevole.
Robotdazero.it - post - R.159.3.4.0