Data logger ESP8266: misurazioni di temperatura e umidità, archiviazione su SD card e accesso remoto con API REST

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

  1. 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.
  2. 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
  3. 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.
  4. 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
  5. 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:

  1. Orologi in Tempo Reale (RTC): è comunemente utilizzato per aggiungere capacità di marcatura temporale precisa a dispositivi come orologi digitali.
  2. Sveglie digitali: il DS3231 può essere impiegato per creare sveglie precise che non devono essere resettate frequentemente.
  3. Controllo di dispositivi temporizzati: è utile in applicazioni che richiedono attivazioni o disattivazioni programmate.
  4. Data logger: può essere utilizzato per annotare i dati con marcature temporali in progetti di registrazione dati.
  5. 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:

  1. 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.
  2. MOSI (Master Out Slave In): questo è il pin attraverso il quale il modulo invia dati al dispositivo master.
  3. SCK (Serial Clock): questo è il pin del clock che sincronizza la trasmissione dei dati tra il modulo e il dispositivo master.
  4. 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:

  1. 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%.
  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%.
  3. Risposta Veloce: grazie alla sua capacità di rispondere rapidamente alle variazioni ambientali, il DHT22 è ideale per monitorare condizioni in tempo reale.
  4. 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.
  5. 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.
  6. 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).
  7. Calibrazione Integrata: il sensore include una calibrazione interna, riducendo la necessità di correzioni esterne e garantendo una maggiore accuratezza nelle misurazioni.
  8. 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:

Pinout del NodeMCU ESP8266
Pinout del NodeMCU ESP8266

Vediamo anche i pinout degli altri componenti:

Il pinout del modulo RTC
Il pinout del modulo RTC
Il pinout del modulo micro SD card
Il pinout del modulo micro SD card

Il pinout del sensore DHT22:

Pinout del DHT22
Pinout del DHT22

Vediamo ora lo schema elettrico del progetto, realizzato come al solito con Fritzing:

Schema elettrico Fritzing del data logger ESP8266
Schema elettrico 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 la libreria RTClib by Adafruit
Installa la libreria RTClib by Adafruit

Installa anche la libreria ArduinoJson by Benoit Blanchon:

Libreria ArduinoJson by Benoit Blanchon
Libreria ArduinoJson by Benoit Blanchon

e la libreria Adafruit BusIO by Adafruit:

Libreria Adafruit BusIO by Adafruit
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:

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).

La board ci fornisce il suo indirizzo IP
La board ci fornisce il suo indirizzo IP

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.

Lista delle reti WiFi disponibili
Lista delle reti WiFi disponibili

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:

La schermata del browser per scegliere la rete
La schermata del browser per scegliere la rete

Clicca il bottone ConfigureWiFi. Ti mostrerà le reti disponibili:

Lista delle reti disponibili
Lista delle reti disponibili

Scegli la SSID della tua rete:

Scegli la tua rete
Scegli la tua rete

Inserisci la password della tua rete e clicca il bottone save:

Inserisci la password
Inserisci la password

La risposta della board
La risposta della board

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:

Ricaviamo l'IP della board
Ricaviamo l’IP della board

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:

Schermata iniziale di Postman
Schermata iniziale di Postman

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:

API getDate
API getDate

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:

API getTH
API getTH

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:

API fileExists
API fileExists

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:

API setDate
API setDate

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:

API fileRead
API fileRead

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:

  1. 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.
  2. 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.
  3. 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.).
  4. 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.
  5. Dati: le righe successive contengono i dati effettivi. Ogni riga rappresenta un record e ogni campo (colonna) contiene un valore specifico.
  6. 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.

Inserisci il tuo nome
Inserisci la tua email
Torna in alto