Introduzione
Benvenuti nel progetto del data logger ESP8266. In questo articolo, esploreremo approfonditamente la creazione di un data logger avanzato gestito da un ESP8266. La nostra missione è costruire un dispositivo in grado di registrare con precisione le variazioni di temperatura e umidità ambientali a intervalli regolari e archiviarle in modo ordinato su una scheda SD. L’ESP8266, rinomato per la sua flessibilità e capacità di connessione Wi-Fi, non solo si occupa del logging locale ma offre anche un’interfaccia API REST. Questa funzionalità apre le porte a un monitoraggio e una gestione intelligenti del nostro registratore dati da remoto.
Un aspetto rilevante di questo progetto è l’organizzazione sulla scheda SD di cartelle e file , strutturata in base all’anno, al mese e al giorno correnti. Ogni file CSV contiene dati dettagliati, quali temperatura, umidità, ora e minuti, registrati ogni mezz’ora. Una caratteristica distintiva è la possibilità di consultare facilmente le misurazioni senza dover spegnere il dispositivo ed estrarre la scheda SD, grazie a un sistema di API REST. Inoltre le API REST consentono di verificare e aggiornare l’orologio in tempo reale, ottenere dati di temperatura e umidità attuali o recuperare i file di log in modo intuitivo e rapido.
Un punto chiave di questo progetto è la flessibilità e la praticità, offrendo un’interfaccia intuitiva per l’utente. Che tu sia un hobbista curioso o un esperto del settore IoT, questo data logger ESP8266 ti offre una soluzione potente e scalabile per il monitoraggio ambientale. Scopriamo insieme come mettere in pratica questo dispositivo versatile, aprendo la strada a numerose applicazioni nel vasto mondo dell’Internet delle cose.
Principali funzionalità del nostro data logger ESP8266
- Registrazione Precisa dei Dati: il data logger è in grado di rilevare con precisione le variazioni ambientali di temperatura e umidità a intervalli regolari di mezz’ora.
- Struttura Organizzata delle Cartelle e dei File: la scheda SD organizza le informazioni in modo intuitivo, suddividendole in directory corrispondenti all’anno e al mese in corso, con file giornalieri che contengono dettagliati dati di temperatura e umidità. Quindi, per fare un esempio, le misurazioni di un determinato giorno (per esempio il 26 gennaio 2024) si troveranno nel file con path templog/24/01/26
- Connettività Wi-Fi: grazie all’ESP8266, il progetto offre la possibilità di gestire i dati a distanza tramite connettività Wi-Fi. Questo permette di monitorare e recuperare informazioni in tempo reale senza dover fisicamente manipolare la scheda SD.
- Misurazioni Precise Ogni 30 Minuti: effettuare misurazioni regolari ogni mezz’ora fornisce una visione dettagliata delle condizioni ambientali nel corso del tempo ed è un ottimo compromesso con la grandezza del file generato che conterrà al massimo 48 misurazioni, risultando un file abbastanza piccolo da essere richiamato agevolmente tramite una apposita API
- Interfaccia API REST: l’aggiunta di un’interfaccia API REST amplia le possibilità di gestione remota. Consente di sincronizzare l’orologio in tempo reale, ottenere aggiornamenti istantanei di temperatura e umidità, o recuperare facilmente i file di log senza sforzi aggiuntivi.
Come negli altri casi, useremo l’IDE PlatformIO per lo sviluppo del firmware mentre per testare le API REST useremo il programma Postman.
Il set di API REST a disposizione
Il dispositivo comunica con l’esterno tramite un set di 5 API REST, 2 di tipo GET e 3 di tipo POST:
- getDate (GET) restituisce un documento Json contenente la data e l’ora correnti nel modulo RTC (anno, mese, giorno, ore, minuti, secondi). Si utilizza per controllare l’esattezza della data e dell’ora;
- getTH (GET) restituisce un documento Json contenente la temperatura e l’umidità correnti. Utile per fare una misurazione al volo senza dover scaricare l’intero file della giornata;
- fileExists (POST) restituisce vero o falso a seconda che un certo file sia presente nel file system o meno. I dati per permettergli di costruire il path corretto del file sono anno, mese, giorno inviati tramite un apposito Json del tipo:
{
"year": "24",
"month" : "01",
"day" : "27"
}
- fileRead (POST) restituisce il contenuto di un determinato file. I dati per permettergli di costruire il path corretto del file sono anno, mese, giorno inviati tramite un apposito Json del tipo:
{
"year": "24",
"month" : "01",
"day" : "27"
}
- setDate (POST) regola l’orologio con i dati di anno, mese, giorno, ore, minuti e secondi forniti con un Json del tipo:
{
"year": "24",
"month" : "01",
"day" : "27",
"hour" : "15",
"minutes" : "12",
"seconds" : "10"
}
Di che componenti abbiamo bisogno per nostro data logger?
La lista dei componenti non è particolarmente lunga:
- una breadboard per connettere la NodeMCU ESP8266 agli altri componenti
- alcuni fili DuPont (maschio – maschio, maschio – femmina, femmina – femmina)
- un sensore DHT22
- un resistore da 4.7 kΩ
- un modulo RTC DS3231
- un modulo di lettura/scrittura di micro SD card con interfaccia SPI
- una micro SD card da non più di 32GB formattata in FAT32
- e, ovviamente, una NodeMCU ESP8266 !
Vediamo ora più in dettaglio questi componenti.
Il modulo DS3231
L’RTC DS3231 (Real-Time Clock DS3231) è un componente elettronico molto utilizzato per tenere traccia del tempo in applicazioni embedded. La sua interfaccia I2C (Inter-Integrated Circuit) lo rende facile da integrare con microcontrollori, come l’Arduino, e altri dispositivi digitali (tipo ESP8266 e ESP32). Quello utilizzato in questo articolo è prodotto dall’azienda AZDelivery. Vediamo una descrizione accurata del DS3231:
Precisione estrema
Il DS3231 è noto per la sua straordinaria precisione nel tenere traccia del tempo. Ha un errore massimo di appena alcuni secondi all’anno, il che lo rende ideale per applicazioni che richiedono marcature temporali accurate.
Interfaccia I2C
L’RTC DS3231 comunica tramite l’interfaccia I2C (o I-squared-C). L’I2C è un protocollo di comunicazione seriale che consente di collegare più dispositivi su un singolo bus, rendendo il DS3231 ideale per progetti che richiedono una gestione semplice e efficiente del tempo.
Calendario completo
Oltre a tenere traccia dell’orario, il DS3231 gestisce anche un calendario completo, compresi giorni della settimana, mesi e anni, considerando anche gli anni bisestili. Questa funzionalità lo rende utile in applicazioni come sveglie, calendari digitali e orologi in tempo reale.
Memoria EEPROM integrata
Il DS3231 è dotato di una piccola memoria EEPROM (Electrically Erasable Programmable Read-Only Memory) che può essere utilizzata per memorizzare dati aggiuntivi. Questa memoria è non volatile, il che significa che i dati rimangono conservati anche in assenza di alimentazione elettrica.
Allarme configurabile
Puoi configurare due allarmi separati sul DS3231, consentendo al dispositivo di generare un segnale di interruzione o un segnale di allarme quando determinate condizioni di tempo sono soddisfatte. Questa funzione è utile in applicazioni come sveglie o controlli di temporizzazione.
Temperatura integrata
Il DS3231 dispone anche di un sensore di temperatura integrato. Questo sensore può essere utilizzato per monitorare la temperatura ambiente ed è particolarmente utile quando la precisione della temperatura è importante per un’applicazione.
Bassa alimentazione e backup batteria
Per preservare la precisione del tempo anche in caso di interruzione dell’alimentazione principale, il DS3231 può essere alimentato da una batteria tampone. Questa batteria garantisce che il dispositivo continui a funzionare e a tenere traccia del tempo anche quando l’alimentazione principale è interrotta.
Applicazioni comuni
Il DS3231 è ampiamente utilizzato in una vasta gamma di applicazioni, tra cui:
- Orologi in Tempo Reale (RTC): è comunemente utilizzato per aggiungere capacità di marcatura temporale precisa a dispositivi come orologi digitali.
- Sveglie digitali: il DS3231 può essere impiegato per creare sveglie precise che non devono essere resettate frequentemente.
- Controllo di dispositivi temporizzati: è utile in applicazioni che richiedono attivazioni o disattivazioni programmate.
- Data logger: può essere utilizzato per annotare i dati con marcature temporali in progetti di registrazione dati.
- Sistemi di automazione domestica: può essere integrato in sistemi di automazione domestica per programmare azioni basate sull’orario.
In sintesi, il DS3231 è un componente altamente affidabile e preciso per il monitoraggio del tempo e della data in applicazioni elettroniche. La sua interfaccia I2C semplifica l’integrazione con una varietà di dispositivi, rendendolo una scelta popolare per progetti basati su microcontrollori.
Il modulo micro SD card
Il modulo micro SD card è un componente elettronico progettato per essere utilizzato con schede Arduino e altre piattaforme di sviluppo compatibili. Questo modulo consente di leggere e scrivere dati su schede di memoria Micro SD e Micro SDHC (TransFlash) utilizzando la comunicazione SPI (Serial Peripheral Interface). Quello utilizzato in questo articolo è prodotto dall’azienda AZDelivery.
Ecco una descrizione dettagliata di questo modulo:
Interfaccia SPI
Il modulo di lettore di schede SD utilizza l’interfaccia SPI, che è un protocollo seriale di comunicazione a 4 fili comunemente utilizzato in progetti embedded. Questi quattro fili sono:
- MISO (Master In Slave Out): questo è il pin attraverso il quale il modulo riceve dati dal dispositivo master, che di solito è l’Arduino o un altro microcontrollore.
- MOSI (Master Out Slave In): questo è il pin attraverso il quale il modulo invia dati al dispositivo master.
- SCK (Serial Clock): questo è il pin del clock che sincronizza la trasmissione dei dati tra il modulo e il dispositivo master.
- CS (Chip Select): questo pin viene utilizzato per selezionare il modulo di lettura della scheda SD e inizializzare le operazioni di lettura/scrittura.
Supporto per Micro SD e Micro SDHC
Questo modulo è compatibile sia con le schede Micro SD che con le schede Micro SDHC, consentendo l’utilizzo di schede con capacità fino a 32 GB. Le schede Micro SD sono comunemente disponibili e offrono un’ampia capacità di archiviazione per dati come file di log, immagini, audio o qualsiasi altra informazione che desideri registrare.
Facile da utilizzare
Il Modulo Lettore SD è facile da integrare nei tuoi progetti Arduino. Viene fornito con una libreria Arduino preinstallata, che semplifica notevolmente la lettura e la scrittura dei dati sulle schede micro SD. Questa libreria consente di accedere facilmente ai file presenti sulla scheda e di effettuare operazioni come la creazione, la lettura, la modifica e l’eliminazione dei file.
LED di stato
Il modulo è dotato di un LED di stato che indica quando il modulo è attivo e in comunicazione con il dispositivo master. Questo LED può essere utile per il debug e il monitoraggio delle operazioni di lettura/scrittura.
Applicazioni comuni
Questo modulo di lettore di schede micro SD è ampiamente utilizzato in una serie di progetti, tra cui:
- Data logging: per registrare dati da sensori o altre fonti su una scheda micro SD per analisi future.
- Lettura di file multimediali: per leggere file audio o immagini da schede micro SD per la riproduzione o la visualizzazione su dispositivi.
- Progetti IoT: in progetti basati su Internet delle cose (IoT) per registrare dati ambientali o di sensori su schede micro SD.
- Registrazione video: in sistemi di registrazione video basati su Arduino o microcontrollori simili.
Il Modulo SD card è un componente utile e pratico per progetti che richiedono la lettura e la scrittura di dati su schede di memoria Micro SD. La sua compatibilità con Arduino e altre piattaforme di sviluppo lo rende un’aggiunta preziosa per progetti che richiedono l’archiviazione e la gestione dei dati. Grazie alla sua facilità d’uso e al supporto per schede di memoria di grandi dimensioni, è una scelta popolare tra gli appassionati di elettronica e gli sviluppatori.
Il sensore DHT22
Ecco una panoramica tecnica dettagliata delle caratteristiche principali del sensore DHT22:
- Precisione delle Misure: il DHT22, noto anche come AM2302, è un sensore di temperatura e umidità digitale che offre una precisione notevole nelle misurazioni. La precisione della temperatura è di ±0.5°C, mentre per l’umidità è di ±2%.
- Ampio Range di Misura: il sensore è in grado di misurare la temperatura in un range che va da -40°C a 80°C, coprendo quindi una vasta gamma di condizioni ambientali. Per quanto riguarda l’umidità, il range è compreso tra 0% e 100%.
- Risposta Veloce: grazie alla sua capacità di rispondere rapidamente alle variazioni ambientali, il DHT22 è ideale per monitorare condizioni in tempo reale.
- Segnali Digitali: il sensore trasmette i dati direttamente in forma digitale, semplificando l’interfacciamento con microcontroller come l’ESP8266. Questo evita la necessità di convertitori analogico-digitali esterni.
- Affidabilità e Durata: progettato per offrire una lunga durata, il DHT22 è caratterizzato da una buona affidabilità nel tempo, rendendolo adatto per applicazioni di monitoraggio a lungo termine.
- Facilità di Utilizzo: con la sua interfaccia di comunicazione a singolo filo, il DHT22 semplifica l’integrazione nei progetti elettronici. La comunicazione avviene attraverso un protocollo di comunicazione a due fili (data e terra).
- Calibrazione Integrata: il sensore include una calibrazione interna, riducendo la necessità di correzioni esterne e garantendo una maggiore accuratezza nelle misurazioni.
- Ampia Applicabilità: grazie alle sue caratteristiche e alla sua facilità di integrazione, il DHT22 è utilizzato in una varietà di applicazioni, tra cui stazioni meteorologiche personali, sistemi di climatizzazione, e progetti di monitoraggio ambientale.
Realizzazione del progetto
Lo schema elettrico
Prima di realizzare il circuito vero e proprio diamo un’occhiata al pinout della board:
Vediamo anche i pinout degli altri componenti:
Il pinout del sensore DHT22:
Vediamo ora lo schema elettrico del progetto, realizzato come al solito con Fritzing:
È possibile che qualche modulo abbia bisogno di qualche connettore e quindi si renda necessario fare qualche saldatura. Se sei nuovo a questo argomento ti consiglio di dare una lettura all’articolo Un altro tutorial su come saldare.
Come puoi osservare, il modulo micro SD card viene alimentato a 5V dal terminale Vin mentre il modulo RTC, come anche il sensore DHT22, è alimentato a 3.3V dal terminale 3V3 della ESP8266.
Il modulo micro SD card è collegato alla porta SPI della ESP8266 che impiega i pin:
- D8 per il terminale CS (Chip Select)
- D7 per il terminale MOSI (Master Out Slave In)
- D6 per il terminale MISO (Master In Slave OUT)
- D5 per il terminale SCK (Serial Clock)
Il modulo RTC è collegato alla porta I2C della ESP8266 che impiega i pin:
- D1 per il terminale SCL
- D2 per il terminale SDA
Il sensore DHT22 è collegato invece al GPIO D4.
Lo sketch
Creiamo il progetto PlatformIO
Abbiamo già visto la procedura di creazione di un progetto PlatformIO nell’articolo Come creare un progetto per NodeMCU ESP8266 con PlatformIO.
Delle librerie indicate installa la DHT sensor library for ESPx by Bernd Giesecke e la WiFiManager by tzapu. Installa poi la libreria RTClib by Adafruit come indicato nella foto seguente:
Installa anche la libreria ArduinoJson by Benoit Blanchon:
e la libreria Adafruit BusIO by Adafruit:
Ora modifica il file platformio.ini per aggiungere queste due righe:
monitor_speed = 115200
upload_speed = 921600
e queste altre due righe alla fine:
Wire
SPI
in modo che abbia un aspetto del genere:
[env:nodemcuv2]
platform = espressif8266
board = nodemcuv2
framework = arduino
monitor_speed = 115200
upload_speed = 921600
lib_deps =
adafruit/RTClib@^2.1.3
adafruit/Adafruit BusIO@^1.15.0
beegee-tokyo/DHT sensor library for ESPx@^1.19
bblanchon/ArduinoJson@^7.0.2
tzapu/WiFiManager@^0.16.0
Wire
SPI
Ovviamente puoi scaricare il progetto dal link seguente:
ESP8266 data logger rtc sdcard api
Sostituisci il file main.cpp del progetto che hai creato con quello presente nel file zip.
Vediamo ora come funziona lo sketch.
Inizialmente vengono incluse le librerie necessarie:
#include <Arduino.h>
#include "DHTesp.h"
#include <Wire.h>
#include <RTClib.h> // Library for the DS3231 RTC
#include <SPI.h> // Include the SPI.h library
#include <SD.h>
#include <ESP8266WebServer.h>
#include <ArduinoJson.h>
#include <WiFiManager.h>
Poi viene istanziato l’oggetto myFile di tipo File che consente di gestire i file nella SD card, viene definito il GPIO D8 per il terminale Chip Select della SD card e viene definito il path di base (templog/) per tutti i file gestiti nella SD card:
// set up variables using the SD utility library functions:
File myFile;
const int chipSelect = D8;
String pathbase = "templog/";
Il file system sarà quindi organizzato in cartelle e file che derivano dall’anno, il mese e il giorno correnti. Per esempio, il path templog/24/01/26 indica il giorno 26 gennaio 2024 e tale file conterrà le misurazioni di quella sola giornata. La creazione delle cartelle e dei file viene effettuata dal data logger in automatico. Se una cartella non c’è, viene creata all’occorrenza (per esempio, se si passa da gennaio a febbraio, il data logger creerà la cartella 02 all’interno della cartella 24 e all’interno della cartella 02 i file 01, 02, 03 ……che rappresentano i giorni di febbraio). Al cambio dell’anno, passando dal 2024 al 2025 il data logger creerà la cartella 25 allo stesso livello della cartella 24 e così via. In questo modo i file sono ordinati e facilmente rintracciabili.
Subito dopo viene istanziato l’oggetto dht che gestisce il sensore e definito il GPIO di collegamento al sensore:
// Set the DHT22 sensor pin
DHTesp dht;
#define DHT22_PIN D4
Vengono poi definite le variabili che stabiliscono la temporizzazione delle misure. Esse sono effettuate ogni 30 minuti in modo da avere 48 misure per giorno (cioè 48 righe per file) in modo da avere un buon compromesso fra la frequenza delle misure e la grandezza del file generato:
unsigned long measureDelay = 1800000; // NOT LESS THAN 2000!!!!! BETTER 1800000 (30 MINUTES)
unsigned long lastTimeRan;
Viene poi istanziato l’oggetto rtc che gestisce l’orologio e la variabile globale readFile che serve a contenere il file richiesto tramite apposita API REST per poi restituirlo come response:
RTC_DS3231 rtc;
String readFile = "";
Viene definito il webserver in ascolto sulla porta 80 che gestirà le API e il documento Json e un buffer di supporto:
// Web server running on port 80
ESP8266WebServer server(80);
StaticJsonDocument<1024> jsonDocument;
char buffer[1024];
Incontriamo poi la funzione:
void addJsonObject(char *name, float value) {
JsonObject obj = jsonDocument.createNestedObject();
obj["name"] = name;
obj["value"] = value;
}
che aggiunge elementi al documento Json durante la sua creazione. Documento Json che poi sarà la response di una API.
La funzione getDate crea un Json contenente la data e l’ora correnti in modo da controllare l’esattezza del modulo RTC. Il documento Json da essa creato viene usato come response per l’API getDate:
void getDate() {
DateTime now = rtc.now(); // Read the date and time from the DS3231
jsonDocument.clear(); // Clear json buffer
addJsonObject("year", now.year());
addJsonObject("month", now.month());
addJsonObject("day", now.day());
addJsonObject("hour", now.hour());
addJsonObject("minutes", now.minute());
addJsonObject("seconds", now.second());
serializeJson(jsonDocument, buffer);
server.send(200, "application/json", buffer);
}
La funzione fileIsPresent controlla che un dato file di cui si fornisce il path sia presente o meno nel file system:
bool fileIsPresent(String path) {
return SD.exists(path);
}
Viene per esempio usata dall’API fileExists.
La funzione getTH crea un Json contenente i valori correnti di temperatura ed umidità. Il documento Json da essa creato viene usato come response per l’API getTH:
void getTH() {
float h = dht.getHumidity(); // reads humidity and puts the value in the h variable
float t = dht.getTemperature();
jsonDocument.clear(); // Clear json buffer
addJsonObject("temperature", t);
addJsonObject("humidity", h);
serializeJson(jsonDocument, buffer);
server.send(200, "application/json", buffer);
}
La funzione setDate prende in ingresso un Json contenente anno, mese, giorno, ore, minuti e secondi e regola l’orologio con questi dati. Questo nel caso volessimo regolare l’orologio manualmente. Tale funzione viene chiamata dall’API setDate:
void setDate() {
if (server.hasArg("plain") == false) {
//handle error here
}
String body = server.arg("plain");
deserializeJson(jsonDocument, body);
// Get code for resistance
String year_s = jsonDocument["year"];
String month_s = jsonDocument["month"];
String day_s = jsonDocument["day"];
String hour_s = jsonDocument["hour"];
String minutes_s = jsonDocument["minutes"];
String seconds_s = jsonDocument["seconds"];
rtc.adjust(DateTime(year_s.toInt(), month_s.toInt(), day_s.toInt(), hour_s.toInt(), minutes_s.toInt(), seconds_s.toInt()));
// Respond to the client
server.send(200, "application/json", "{}");
}
La funzione fileExists controlla l’esistenza di un determinato file a partire dal path creato con l’anno, il mese e il giorno. Essa si avvale della funzione fileIsPresent incontrata precedentemente ed è chiamata dall’API fileExists:
void fileExists() {
if (server.hasArg("plain") == false) {
//handle error here
}
String body = server.arg("plain");
deserializeJson(jsonDocument, body);
// Get code for resistance
String year = jsonDocument["year"];
String month = jsonDocument["month"];
String day = jsonDocument["day"];
String path = "templog/" + year + "/" + month + "/" + day;
String res = "";
if(fileIsPresent(path)) {
res = "true";
} else {
res = "false";
}
// Respond to the client
String response = "{\"isPresent\" : " + res + "}";
server.send(200, "application/json", response);
}
La funzione readfile legge il file presente al path dato e lo memorizza nella variabile readFile. Viene chiamata dall’API fileRead:
void readfile(String path) {
readFile = "";
char patharray[path.length() + 1];
path.toCharArray(patharray, path.length() + 1);
SD.begin(chipSelect);
myFile = SD.open(patharray);
if (myFile) {
// read from the file until there's nothing else in it:
for (unsigned long i = 0; i <= myFile.size() - 1; i++) {
myFile.seek(i);
readFile = readFile + (char)myFile.peek();
}
// close the file:
myFile.close();
}
}
La funzione fileRead è l’API vera e propria che restituisce il contenuto del file letto dalla funzione readfile:
void fileRead() {
if (server.hasArg("plain") == false) {
//handle error here
}
String body = server.arg("plain");
deserializeJson(jsonDocument, body);
// Get code for resistance
String year = jsonDocument["year"];
String month = jsonDocument["month"];
String day = jsonDocument["day"];
String path = "templog/" + year + "/" + month + "/" + day;
String res = "";
if(fileIsPresent(path)) {
readfile(path);
String response = "{\"file\" : \"";
readFile.trim();
response += readFile;
response += "\"}";
server.send(200, "application/json", response);
} else {
server.send(404, "application/json", "{}");
}
}
La funzione measure_sd legge i valori correnti di temperatura e umidità e compone parte della stringa da salvare sul file (non si occupa dell’ora e dei minuti):
String measure_sd() {
char temperature_c[6];
char humidity_c[6];
float h = dht.getHumidity(); // reads humidity and puts the value in the h variable
float t = dht.getTemperature(); // reads temperature and puts the value in the t variable
dtostrf(t, 0, 1, temperature_c); // Here we need to convert the float to a string
dtostrf(h, 0, 1, humidity_c); // Here we need to convert the float to a string
return (String)temperature_c + "," + (String)humidity_c;
}
La funzione sanitize fa sì che i valori di mesi, giorni, ore, minuti e secondi abbiano sempre due cifre, anteponendo uno zero nel caso di numeri ad una cifra. Così “1” diventa “01” e così via:
String sanitize(String hms) {
if(hms.length() < 2) {
hms = "0" + hms;
}
return hms;
}
La funzione updatefile crea, se non presente, il file col path corretto ricavato dalla data corrente e scrive i valori di temperatura e umidità prendendo la stringa creata dalla funzione measure_sd e concatenandola con l’ora e i minuti correnti. Se il file è invece già presente si limita a scrivere i nuovi dati (append) su di esso fino a completarlo:
void updatefile() {
DateTime now = rtc.now();
String path_folder = pathbase + sanitize(((String)now.year()).substring(2,5)) + "/" + sanitize((String)now.month()) + "/";
String path_file = path_folder + sanitize((String)now.day());
String line = measure_sd() + "," + sanitize((String)now.hour()) + ":" + sanitize((String)now.minute());
SD.begin(chipSelect);
char path_folder_array[path_folder.length() + 1];
path_folder.toCharArray(path_folder_array, path_folder.length() + 1);
char path_file_array[path_file.length() + 1];
path_file.toCharArray(path_file_array, path_file.length() + 1);
if(!SD.exists(path_folder_array)) {SD.mkdir(path_folder_array);}
myFile = SD.open(path_file_array, FILE_WRITE);
// if the file opened okay, write to it:
if (myFile) {
myFile.println(line);
// close the file:
myFile.close();
}
}
La funzione setupApi associa le 5 API /getDate, /getTH, /fileExists, /fileRead e /setDate alle rispettive funzioni getDate, getTH, fileExists, fileRead, setDate:
void setupApi() {
server.on("/getDate", getDate);
server.on("/getTH", getTH);
server.on("/fileExists", HTTP_POST, fileExists);
server.on("/fileRead", HTTP_POST, fileRead);
server.on("/setDate", HTTP_POST, setDate);
// start server
server.begin();
}
La funzione setup comincia con l’inizializzazione della porta seriale, la connessione del DHT22 al GPIO D4 e l’inizializzazione dei moduli Wire e rtc:
Serial.begin(115200);
delay(2000);
dht.setup(DHT22_PIN, DHTesp::DHT22); // Connect DHT sensor to GPIO D4
Wire.begin();
rtc.begin();
Continua poi con l’inizializzazione della SD card:
// Initialize SPI communication with the SD module
if (!SD.begin(chipSelect)) {
Serial.println("Error initializing the SD card.");
return;
}
Serial.println("SD card initialized successfully.");
e la regolazione dell’orologio inviandogli l’orario del computer:
if (rtc.lostPower()) {
Serial.println("RTC not initialized. I set the time...");
rtc.adjust(DateTime(F(__DATE__), F(__TIME__)));
}
Segue poi tutta la parte della gestione della connessione WiFi:
WiFi.mode(WIFI_STA); // explicitly set mode, esp defaults to STA+AP
// it is a good practice to make sure your code sets wifi mode how you want it.
//WiFiManager, Local intialization. Once its business is done, there is no need to keep it around
WiFiManager wm;
// reset settings - wipe stored credentials for testing
// these are stored by the esp library
// wm.resetSettings();
// Automatically connect using saved credentials,
// if connection fails, it starts an access point with the specified name ( "AutoConnectAP"),
// if empty will auto generate SSID, if password is blank it will be anonymous AP (wm.autoConnect())
// then goes into a blocking loop awaiting configuration and will return success result
bool res;
// res = wm.autoConnect(); // auto generated AP name from chipid
// res = wm.autoConnect("AutoConnectAP"); // anonymous ap
res = wm.autoConnect("AutoConnectAP","password"); // password protected ap
if(!res) {
Serial.println("Failed to connect");
ESP.restart();
}
else {
//if you get here you have connected to the WiFi
Serial.println("Connected...yeey :)");
}
e la chiamata alla funzione setupApi() che serve ad instradare le varie richieste REST verso le funzioni appropriate (come già visto poco più sopra).
La funzione loop, semplicissima, richiama ciclicamente la funzione del server che gestisce le richieste dal client (il nostro programma Postman) e fa l’update del file sulla SD card ogni measureDelay ms:
void loop() {
// put your main code here, to run repeatedly:
server.handleClient();
if (millis() > lastTimeRan + measureDelay) {
updatefile();
lastTimeRan = millis();
}
}
Come connettere la board ad Internet
Dopo aver caricato lo sketch sulla board, apri il Serial Monitor per vedere i messaggi provenienti dal dispositivo.
Per prima cosa la board va in modalità Access Point e ci fornirà un indirizzo IP che useremo a breve. Questa operazione serve per connettere la board ad Internet senza dover inserire nel codice i parametri della rete WiFi (SSID e password).
In questo caso l’indirizzo IP è 192.168.4.1.
A questo punto la ESP8266 è in modalità Access Point (con SSID AutoConnectAP) e dobbiamo connettere il nostro computer alla rete AutoConnectAP. Se andiamo nel menu delle reti del nostro computer, dovremmo vedere anche la rete AutoConnectAP nell’elenco delle reti wireless.
Connetti il computer alla rete AutoConnectAP. Quindi vai sul tuo browser e inserisci l’IP precedentemente fornito dalla ESP8266 (che in questo esempio è 192.168.4.1)
Vedrai una schermata come questa:
Clicca il bottone ConfigureWiFi. Ti mostrerà le reti disponibili:
Scegli la SSID della tua rete:
Inserisci la password della tua rete e clicca il bottone save:
Il modulo ESP8266 conserva memorizzati i parametri di accesso anche se lo spegni, li ricorderà al riavvio e si ricollegherà automaticamente senza dover ripetere questa procedura. Solo se lo resetti rimuovendo il commento da questa riga
// wm.resetSettings();
perderà i parametri di connessione.
Nota Bene: il dispositivo può memorizzare solo una rete. Se successivamente lo colleghi a un’altra rete, dimenticherà le impostazioni della rete precedente.
Testiamo il progetto con le API REST
Una volta che la ESP8266 è stata connessa alla rete WiFi ci fornirà tramite il Serial Monitor di PlatformIO il suo indirizzo IP, come visibile nella figura seguente:
In questo caso l’IP assegnato dal router alla board è 192.168.1.9. Tale IP ci servirà per comporre le API REST.
Per interagire con la board abbiamo bisogno di un software apposito che si chiama Postman. Dopo aver installato il programma, siamo pronti ad usarlo.
Ecco come si presenta la sua schermata iniziale:
Nella finestra principale si trova una barra in cui dovrai inserire l’API.
Alla sinistra di questa barra c’è un menù a tendina che consente di scegliere il tipo di API (per esempio GET, POST, PUT…).
NOTA: negli esperimenti successivi è stato assegnato l’IP 192.168.1.121 quindi le immagini seguenti faranno riferimento a questo IP. Tu, ovviamente, dovrai mettere l’indirizzo IP che è stato assegnato alla tua ESP8266.
In generale la struttura dell’API è di questa forma:
http://IP_ESP8266/nomeAPI
Proviamo le API GET. Nel menu a tendina a sinistra scegliamo la voce GET. Proviamo l’API getDate mettendo la riga
192.168.1.121/getDate
nella casella a destra del menu GET e pigiamo il tasto Send. Se tutto va bene otterremo una risposta come questa:
Ora proviamo la getTH. Mettiamo la riga:
192.168.1.121/getTH
nella casella a destra del menu GET e pigiamo il tasto Send. Se tutto va bene otterremo una risposta come questa:
Passiamo ora alle API di tipo POST. Nel menu a tendina a sinistra scegliamo il tipo POST.
Esse prevedono di fornire come dato di ingresso dell’API un documento Json. Per fornire questo Json, dovrai selezionare la voce Body che sta sotto la barra dell’URL. Poi seleziona la voce raw (sotto Body) e poi, sul menù a tendina sulla destra, seleziona la voce JSON al posto di Text.
Vediamolo con la prima API POST, la fileExists. Mettiamo la riga
192.168.1.121/fileExists
inseriamo il file Json come spiegato poco sopra e pigiamo il tasto Send.
La risposta dovrebbe essere qualcosa del genere:
Per regolare l’orologio usiamo la setDate sempre inserendo il Json come spiegato sopra. Usando la riga
192.168.1.121/setDate
dovremmo quindi avere una situazione analoga a questa:
Premendo il tasto Send l’orologio verrà settato con i dati del Json in ingresso.
Infine leggiamo un dato file col comando
192.168.1.121/fileRead
sempre inserendo il Json contenente l’anno, il mese e il giorno per identificarlo nel file system.
La situazione sarà di questo tipo:
In questo caso specifico ottengo una risposta di questo tipo:
{"file" : "18.7,62.2,00:03
18.4,61.8,00:33
18.1,61.2,01:03
17.9,60.7,01:33
17.6,62.2,02:03
17.4,61.9,02:33
17.3,60.3,03:03
17.2,61.1,03:33
17.1,61.2,04:03
16.9,61.2,04:33
16.9,60.9,05:03
16.7,60.5,05:33
16.7,60.5,06:03
16.5,60.5,06:33
16.4,60.2,07:03
16.3,60.3,07:33
16.3,60.5,08:03
16.2,61.7,08:33
16.2,61.7,09:03
15.9,62.1,09:33
16.0,63.4,10:03
16.1,64.2,10:33
16.0,64.7,11:03
16.1,65.8,11:33
16.1,65.7,12:03
16.6,65.7,12:33
16.4,66.4,13:03
16.5,70.6,13:33
16.6,70.9,14:03
16.6,72.6,14:33
16.7,71.6,15:03
16.7,71.6,15:33
16.6,71.5,16:03
16.6,71.1,16:33
16.6,71.0,17:03
16.6,70.1,17:33
16.5,71.2,18:03"}
Ripulendo il file eliminando la parte iniziale {“file” : “ e quella finale “} otteniamo un file CSV contenente i dati della giornata:
18.7,62.2,00:03
18.4,61.8,00:33
18.1,61.2,01:03
17.9,60.7,01:33
17.6,62.2,02:03
17.4,61.9,02:33
17.3,60.3,03:03
17.2,61.1,03:33
17.1,61.2,04:03
16.9,61.2,04:33
16.9,60.9,05:03
16.7,60.5,05:33
16.7,60.5,06:03
16.5,60.5,06:33
16.4,60.2,07:03
16.3,60.3,07:33
16.3,60.5,08:03
16.2,61.7,08:33
16.2,61.7,09:03
15.9,62.1,09:33
16.0,63.4,10:03
16.1,64.2,10:33
16.0,64.7,11:03
16.1,65.8,11:33
16.1,65.7,12:03
16.6,65.7,12:33
16.4,66.4,13:03
16.5,70.6,13:33
16.6,70.9,14:03
16.6,72.6,14:33
16.7,71.6,15:03
16.7,71.6,15:33
16.6,71.5,16:03
16.6,71.1,16:33
16.6,71.0,17:03
16.6,70.1,17:33
16.5,71.2,18:03
Come già menzionato, il file è in formato CSV. Questo perché un file CSV (Comma-Separated Values) è un tipo di file di testo utilizzato per rappresentare dati tabulari, come tabelle o fogli di calcolo, in cui le colonne di dati sono separate da virgole o altri delimitatori. Ecco una descrizione di un file CSV:
- Testo puro: un file CSV è un file di testo puro che contiene dati in formato tabellare. Può essere aperto e letto con qualsiasi editor di testo.
- Righe e colonne: i dati sono organizzati in righe e colonne. Ogni riga rappresenta un record o un’istanza di dati, mentre le colonne rappresentano i campi o le variabili dei dati.
- Delimitatori: i dati nelle colonne sono separati da delimitatori, di solito virgole (
,
), punto e virgola (;
), o tabulazioni (\t
). Il delimitatore scelto determina il tipo di file CSV (CSV con virgola, CSV con punto e virgola, CSV con tabulazione, ecc.). - Intestazioni: spesso, la prima riga di un file CSV contiene le intestazioni delle colonne, cioè i nomi dei campi. Queste intestazioni sono utili per comprendere il significato dei dati nelle colonne.
- Dati: le righe successive contengono i dati effettivi. Ogni riga rappresenta un record e ogni campo (colonna) contiene un valore specifico.
- Citazioni: in alcuni casi, i valori di una colonna possono essere racchiusi tra virgolette doppie (
"
) per gestire correttamente i valori che contengono il delimitatore stesso o caratteri speciali.
Il file CSV è facilmente apribile come foglio di calcolo con programmi quali Excel o LibreOffice Calc in modo da fare calcoli, statistiche o grafici sui dati raccolti.
Newsletter
Se vuoi essere aggiornato sui nuovi articoli, iscriviti alla newsletter. Prima dell’iscrizione alla newsletter leggi la pagina Privacy Policy (UE)
Se ti vuoi disiscrivere dalla newsletter clicca sul link che troverai nella mail della newsletter.