in Informatica, Tutorial

Raccolta dati da sensori con ESP8266 e Python/Flask tramite HTTP

In questo articolo verrà esposta una semplice metodologia di acquisizione dati da sensori compatibili con ESP8266, utilizzando HTTP come protocollo di comunicazione; gli ESP fungeranno da Client HTTP ed inoltreranno i dati acquisiti ad un server su cui girerà una Web App scritta utilizzando la libreria Flask di Python3.

Prima di iniziare, qui di seguito sono riportati alcuni collegamenti ad articoli di Antima.it che possono tornare utili per muovere i primi passi con ESP8266 tramite l’IDE Arduino:

Inoltre, si è parlato di protocolli applicativi in questo articolo e di programmazione su piattaforma Arduino nella serie Arduino spiegato facile.

Il dispositivo preso in considerazione oggi è il sensore ad ultrasuoni HC-SR04, tramite cui sarà possibile acquisire una misura della distanza di oggetti posti di fronte ad esso; in questo modo si possono costruire sistemi per la previsione di collisioni, per la gestione di informazioni su persone che passano da un punto della casa o qualsiasi altro sistema per cui possa tornare utile un’informazione legata al movimento di un’entità.

Parte 1 – Progettazione

Prima di passare all’implementazione, è utile ragionare un attimo in termini astratti sul come realizzare quanto presentato.  Qui di seguito è riportata una schematizzazione tramite Diagramma di Attività di quanto verrà realizzato; rappresentare il ciclo di vita del programma in questa maniera rende più facile isolare le attività fondamentali che possono essere implementate indipendentemente l’una dall’altra per poi essere connesse tra di loro.

Diagramma di Attività dell’applicazione lato Client
Diagramma di Attività dell’applicazione lato Server

I nostri dispositivi contenenti i sensori andranno ad inoltrare i dati letti al server tramite richieste HTTP PUT, specificando il particolare sensore ed il dato attualmente letto. I dati vengono inoltrati in formato JSON.

Parte 2 – ESP8266

Come anticipato nell’introduzione, l’ESP fungerà da client connettendosi al server per inviare i dati raccolti. Le librerie che verranno utilizzate per la scrittura del programma di controllo dell’ESP saranno ESP8266WiFi.h, ESP8266HTTPClient.h e ArduinoJson.h; le prime due sono automaticamente disponibili dopo aver seguito i tutorial consigliati ad inizio articolo, mentre la terza può essere scaricata dall’IDE di Arduino andando nel seguente percorso del menù contestuale:

Sketch->#include libreria->Gestione librerie…

e cercando ArduinoJson all’interno del form di ricerca.

La prima operazione presa in considerazione osservando il diagramma dell’applicazione Client è quella di connessione al router WiFi; questa viene effettuata tramite l’interfaccia offerta dalla libreria ESP8266WiFi.h, ed in particolare tramite la funzione WiFi.begin(), passando ad essa l’identificatore della rete WiFi e la password. Si vuole potere avere la possibilità di tentare la connessione più volte in diversi punti dello sketch qualora essa cada: in questi casi la migliore cosa da fare è scrivere una funzione che racchiuda la routine di connessione al suo interno. Considerando per semplicità di avere le informazioni sull’identificativo della rete e la password come costanti globali, si può scrivere la seguente funzione di connessione:

const char* SSID = ""; // Qui va inserito il nome della propria rete WiFi
const char* PASSWORD = "";// Qui va inserita la password di rete

void connectToWiFi() {
    WiFi.begin(SSID, PASSWORD);
    while (WiFi.status() != WL_CONNECTED) {
        delay(2000); // 2 secondi prima di ritentare la connessione 
        Serial.println("Tentativo di connessione");
    }
    Serial.println("Connesso alla rete locale!");
}

Prima di passare alla routine di acquisizione dati, bisogna capire come funziona il sensore ad ultrasuoni scelto come dispositivo per effettuare le letture sulla posizione degli oggetti circostanti. L’HC-SR04 ha due pin, TRIGGER e ECHO, tramite cui comandare il sensore e ottenere i dati letti. Mandando un impulso della durata di 10 microsecondi in input sul pin TRIGGER il sensore genererà degli ultrasuoni e aspetterà di ricevere l’onda riflessa.

Una volta che questa verrà ricevuta, o che il tempo limite di attesa verrà oltrepassato, il sensore renderà disponibile tramite il pin ECHO la durata in microsecondi del fenomeno invio/ricezione. Da questa durata, conoscendo che la velocità del suono a 20° C è pari a ~343 m/s (Da Wikipedia) si può ottenere la distanza dell’oggetto incontrato in centimetri tramite la formula:

distanza = 0,034 * tempo_andata_ritorno / 2

considerando che la durata va divisa per due in quanto comprende sia andata che ritorno. Il tutto può essere tradotto in codice nel seguente modo:

const int TRIGGER = 0; // PIN 0 dell'ESP
const int ECHO = 2; // PIN 2 dell'ESP

long tempo, distanza;

void acquireData() {
    digitalWrite(TRIGGER, HIGH);
    delayMicroseconds(10);
   digitalWrite(TRIGGER, LOW);
tempo = pulseIn(ECHO, HIGH);
    distanza = 0.034 * tempo / 2;
}

Una volta acquisito il dato, può far comodo avere una maniera per capire se è il caso di inoltrarlo al server; prevenire l’invio di dati uguali o simili potrebbe essere una buona idea per evitare di congestionare il server. Qui di seguito viene proposta una funzione che compara due valori letti e restituisce true se questi sono abbastanza diversi ma qualsiasi altro approccio andrà altrettanto bene:

const int EQUAL_THRESH = 2; // Soglia di differenza
// Restituisce true se la differenza tra x e y è maggiore
// di EQUAL_THRESH * 10^ordine di grandezza di x
bool almostEquals(long x, long y) {
    float grandezza = (float)x/10;
  if(grandezza <= 10)
        return abs(x-y) < EQUAL_THRESH;
    if(grandezza > 10 && magnitude <= 100)
      return abs(x-y) < EQUAL_THRESH * 10;
    return abs(x-y) < EQUAL_THRESH * 100;
}

A questo punto manca solo un modo per mandare i dati via HTTP in formato JSON.

ArduinoJson permette di formattare i dati in JSON in maniera semplice; ci sono diverse modalità di utilizzo di questa libreria ma questo articolo non ha come obiettivo quello di esplorarle tutte. I dati da inviare sono due: la distanza ed il tempo di andata/ritorno dell’onda; questi dati vanno organizzati all’interno di un’istanza di oggetto StaticJsonDocument. Per inizializzare questo tipo di oggetto è necessario specificarne la capacità: dato che l’applicazione invierà ogni volta un pacchetto contenente due istanze di dato, si può calcolare la capacità necessaria per il documento JSON utilizzando la macro JSON_OBJECT_SIZE(2).

I dati così formattati andranno inoltrati al server HTTP. Ogni sensore viene identificato con un intero non negativo; a partire da questo concetto, ad ogni sensore corrisponderà un indirizzo a cui bisognerà connettersi per aggiornare lo stato delle letture, che avrà il seguente formato:

http://indirizzo_server/sensor/identificativo_sensore

Qui di seguito è proposto il codice che implementa le funzionalità appena introdotte e che andrà a formare il nucleo della funzione loop:

if(WiFi.status() == WL_CONNECTED){
acquireData();

if(!almostEquals(distanza, lastDistanza)) {
HTTPClient http;
http.begin(String(SERVER_ADDR) + String(SENSOR_ID));
      jsonMsg["tempo"] = tempo;
    jsonMsg["distanza"] = distanza;
        serializeJson(jsonMsg, msg);
        http.addHeader("Content-Type", "application/json");
      int respCode = http.PUT(msg);

        if(respCode > 0){
            String resp = http.getString();
            Serial.print(respCode);
            Serial.print(", ");
            Serial.println(resp);
        }else{
            Serial.print("Errore nell'invio della richiesta!");
      }
      http.end();
        lastDistanza = distanza;
    }
}else{
  connectToWiFi();
}
// Questo insieme di operazioni viene ripetuto
// ogni secondo 
delay(1000); 

Parte 3 – Python/Flask

Ora che il sensore è pronto ad essere utilizzato, bisogna fare sì che possa comunicare con qualcosa. Questo “qualcosa” sarà un’applicazione Python3 scritta utilizzando la libreria flask.

Flask è un framework Python che permette di scrivere semplici applicazioni per servire richieste HTTP. Le due librerie che verranno utilizzate in questo articolo saranno flask e flask-restful, che fornirà la possibilità di organizzare e virtualizzare i dati in arrivo con il concetto di risorsa.

Per installare le librerie, usando il gestore di pacchetti di python pip:

pip3 install flask flask-restful

L’applicazione presentata di seguito è molto semplice ed è pensata come esempio iniziale per apprendere le basi di questo framework: è quindi un punto di inizio per chiunque sia curioso e voglia imparare ad utilizzare flask per creare applicazioni complesse.

Un dizionario verrà utilizzato per contenere le varie corrispondenze identificativo_sensore => dati raccolti. Ogni volta che un dato verrà ricevuto, il dizionario verrà aggiornato ed il dato stampato. Tramite la libreria flask-restful è possibile creare delle classi risorse che virtualizzano in questo caso i sensori.

Per ogni risorsa si possono definire dei metodi corrispondenti ai verbi HTTP POST, PUT, GET, PATCH, DELETE che verranno chiamati alla ricezione di una specifica richiesta. Si può poi legare la risorsa ad un indirizzo specifico tramite il metodo add_resource.

Nel caso dell’applicazione di seguito presentata, viene utilizzato anche un oggetto RequestParser che si occuperà di analizzare e formattare i dati in entrata (quelli in formato JSON inviati dall’ESP) per assicurarsi che i loro identificativi (tempo, distanza) siano corretti e che contengano i tipi di dati che ci si aspetta. Per fare partire il tutto bisogna inizializzare un oggetto Flask passando come argomento il valore della variabile __name__ contenente il nome del modulo attuale, inizializzare un oggetto Api e far partire il tutto chiamando il metodo run di flask. Il metodo run viene chiamato impostando come host l’indirizzo 0.0.0.0 affinché l’app sia raggiungibile dall’esterno, sulla porta 80 che è quella standard del protocollo HTTP.

from flask import Flask, request
from flask_restful import Api, Resource, reqparse, abort

app = Flask(__name__)
api = Api(app)

_data = dict()

class SensorApi(Resource):
    parser = reqparse.RequestParser()
    parser.add_argument("tempo", type=int, required=True)
    parser.add_argument("distanza", type=int, required=True)
   
    def put(self, data_id):
        args = self.parser.parse_args()
        print(args)
        _data[data_id] = {"tempo": args["tempo"], "distanza": args["distanza"]}
        return {"id": data_id, "tempo": args["tempo"], "distanza": args["distanza"]}, 201

api.add_resource(SensorApi, "/<data_id>")
app.run(host="0.0.0.0", port=80)

Sperando che l’articolo sia tornato utile, vi invito a lasciare un commento nel caso in cui foste interessati all’argomento o ci fosse bisogno di delucidazioni, grazie e ad al prossimo tutorial!

Scrivi un commento

Commento

  1. Buonasera Gianmarco Marcelo,
    Anche io sto realizzando un sistemino che preleva temperatura, umidità, e pressione, senza usare il sito di think speak(il quale non richiede l’uso della serializzazione dati json). Però io il progetto ce l’ho per il PIC (non per arduino).
    Una volta scritte in php due pagine, una per il metodo get, l’altra per il metodo post entrambi funzionanti; ho provato ad inviare dati con comandi AT, con entrambi i metodi senza esito positivo(il mio database non si aggiorna). In particolare non ho ancora capito come formattare nel modo giusto ciò che voglio inviare con i comandi AT sul modulo esp-01 Wi-Fi. Potresti darmi qualche delucidazione il merito alla sintassi da usare su i comandi AT ? grazie mille.

    • Ciao Leonardo, ti premetto che non ho molta dimestichezza con i comandi AT. Prima di utilizzarli, ti consiglierei di testare che il tuo endpoint GET/POST funzioni tramite strumenti di più facile utilizzo come Postman o cURL, giusto per escludere la possibilità che il tuo problema sia lato php.

      Per quanto riguarda il resto, noi utilizziamo l’approccio via IDE Arduino negli esempi sul sito, un’altra alternativa semplice sarebbe quella di utilizzare il firmware micropython e usare la shell interattiva per questo tipo di test dove si ha bisogno di più spazio di manovra.

      I comandi AT, personalmente, li trovo piuttosto ostici, e secondo me non vale la pena sbattersi contro loro quando devi iniziare a pensare anche a come trasmettere dati formattati ecc.

      Spero di essere riuscito ad esserti di aiuto, grazie per avere lasciato un commento!

  2. Salve Gianmarco, avrei necessità di inviare una semplice stringa tra 2 Nodemcu connessi tra loro come server/client.
    Potresti suggerirmi uno spunto di codice???
    Praticamente un primo Nodemcu rileva su un ingresso lo stato di un pulsante
    e lo invia al secondo Nodemcu che provvede a pilotare un rele.
    Grazie.

    • Ciao Davide, puoi approcciare il problema in diversi modi, a seconda di tanti punti di vista (risparmio energetico, semplicità di realizzazione, architettura della tua app).

      Se vuoi una soluzione veloce e semplice da implementare, ti direi di creare un server http utilizzando una libreria come https://github.com/me-no-dev/ESPAsyncWebServer (trovi un semplice esempio nella cartella examples/simple_server) sul node che deve pilotare il relè, mandando le richieste http da quello legato al pulsante. Per quanto riguarda il client, puoi usare qualcosa di simile a quanto ho inserito in questo stesso articolo!

  3. Interessante questo argomento, sto facendo esperimenti di trasmissione da ESP8266 a cell e mi chiedevo se si poteva, senza dover interrogare esp8266, ricevere lo stato di cambiamento( anche descrittivo) di un evento con una app tipo Telegram (IoT Lan controller)……saprebbe aiutarmi in questo?????
    Grazie

  4. Buongiorno Gianmarco avrei un quesito da porti:

    Dovrei connettere ad un app una scheda tramite esp 01 ma la mia scheda non scritta in linguaggio c ma in assembler, tramite la seriale potrebbe comunicare con l’esp oppure è impossibile??

    • Ciao Vincenzo, hai due opzioni:
      – Puoi connetterti tramite seriale all’ESP01, ed interfacciarti tramite i comandi AT (cerca online ESP01 AT commands, si trova molto a riguardo).
      – Puoi scrivere un firmware in C per l’esp01, e mandare i tuoi dati via seriale allo stesso, dalla tua scheda programmata in assembly. Il firmware sull’ESP gestirebbe così autonomamente la connessione e la seriale, per questo ultimo approccio puoi ispirarti lato connessione a questo articolo.

      Ciao!