in Antima for IoT, Tutorial Python

Acquisizione ed elaborazione dati con Arduino – Un’applicazione browser based

In questa seconda fase della serie verranno proposte delle applicazioni più avanzate della libreria antimait partendo dai concetti introdotti fino ad ora. 

In particolare, in questo articolo ci si concentrerà nella realizzazione di un’applicazione tramite cui visualizzare i grafici dei dati che vengono ricevuti da dei dispositivi Arduino via seriale, aggiornandoli con una certa regolarità, direttamente tramite un web browser come Google Chrome, Mozilla Firefox, ecc.

Gli strumenti che verranno utilizzati qui di seguito saranno un po’ di JavaScript, Python3 e le librerie flask (di cui si è già parlato in un articolo sulla raccolta dati da sensori utilizzando HTTP) e la nostra antimait.

Parte 1 – Descrizione e strumenti

Si vuole realizzare un’applicazione che visualizzi, aggiornandosi periodicamente, lo stato dei dati inviati da tutti i sensori connessi ad un’istanza della classe Gateway contenuta all’interno di antimait.

Nel dettaglio, si vuole far sì che dei grafici vengano generati tramite l’utilizzo di matplotlib, appoggiandosi ad antimait.Plotter, e che questi siano visualizzati solo quando uno specifico sensore sia in funzione.

La modalità scelta per implementare quanto introdotto sarà la seguente:

  • Un applicativo front-end principalmente realizzato in JavaScript che, periodicamente, richieda informazioni sui sensori connessi e sui grafici aggiornati dei dati relativi ad essi, presentandoli poi tramite una pagina html;
  • Un applicazione back-end che avvii l’istanza di antimait.Gateway in un Thread e risponda alle richieste http provenienti dall’applicativo front-end in un’altro, esponendo i grafici più recenti.

Una schematizzazione dell’ambiente preso in considerazione

Nel grafico qui sopra viene presentata un semplice schema esplicativo dell’architettura in questione: i dati vengono acquisiti dagli Arduino ed inoltrati ad una macchina su cui gira la nostra applicazione. L’oggetto istanza di antimait.Gateway è in esecuzione all’interno di questa macchina e genera grafici in una cartella specificata all’interno del programma; ogni qualvolta una richiesta di visualizzazione arriva dal browser, l’applicazione deve controllare quali interfacce siano connesse al gateway e restituire i relativi grafici generati.

Per semplicità, si farà in modo che il corpo della pagina venga aggiornato con una certa frequenza, in maniera tale che i grafici vengano periodicamente ricaricati e visualizzati nella loro versione più recente.

Parte 2 – Back-end (Python Flask+antimait)

L’applicazione per il back-end è scritta in Python utilizzando la struttura tipica dei progetti Flask, con una cartella template che contiene, appunto, i template delle pagine html da servire ed una cartella static che contiene le immagini ed i file JavaScript.

La logica è contenuta all’interno del file app.py riportato qui di seguito:

import pathlib
import logging
import flask

import antimait
from antimait.plotting import Plotter, format_filename

logging.basicConfig(level=logging.INFO)

plots_path = pathlib.Path("static", "plots")
if not plots_path.exists():
plots_path.mkdir()

img_path = str(plots_path)

app = flask.Flask(__name__)
gw = antimait.Gateway()


def on_connect(interface: antimait.CommInterface, description: str) -> None:
plotter = Plotter(interface.ifc_id, img_dir=img_path)
plotter.plot()
interface.attach(plotter)


gw.on_connect = on_connect
gw.listen()


@app.route("/")
def home():
return flask.render_template("index.html")


@app.route("/plots")
def plots():
img_list = ["{}.png".format(format_filename(interface.ifc_id)) for interface in gw.interfaces]
return {"plots": img_list}, 200


if __name__ == "__main__":
app.run(host="0.0.0.0", port=5000)

La prima parte del codice, subito dopo gli import delle librerie, ha il compito di inizializzare il gateway, il framework flask, il logger della libreria antimait e di definire il percorso nel quale trovare le immagini salvate, all’interno della cartella plots dentro static.

Una volta fatto ciò, viene definita la modalità di aggancio dei Plotter al gateway come visto negli articoli precedenti, per poi fare partire il gateway in un Thread separato grazie al metodo listen.

Subito dopo sono presenti due funzioni precedute da dei decoratori speciali che definisco degli endpoint di flask. Quando una pagina viene richiesta dal browser seguendo l’indirizzo specificato, la rispettiva funzione associata viene eseguita ed il risultato viene ritornato tramite risposta http. In questo caso gli endpoint sono solo due e le funzioni associate sono molto semplici:

  • La prima viene eseguita quando viene richiesta la pagina principale del sito, e caricherà la pagina index.html presente nella cartella templates;
  • La seconda viene eseguita richiedendo la pagina /plots e ritorna la lista dei nomi dei file contenenti i grafici generati provenienti dalle schede al momento connesse; questa lista è ritornata all’interno di un oggetto JSON.

All’interno di quest’ultima funzione è presente la riga:

img_list = ["{}.png".format(format_filename(interface.ifc_id)) for interface in gw.interfaces]

che genera la lista dei grafici. Questo tramite l’utilizzo di diverse particolarità delle classi antimait: 

  • gw.interfaces è una lista di tutte le interfacce di comunicazione al momento connesse al gateway;
  • interface.ifc_id è l’identificativo di una specifica interfaccia;
  • format_filename è una funzione contenuta in antimait.plotting che genera un nome file valido a partire dall’identificativo dell’interfaccia; format_filename(interface.ifc_id) è la maniera in cui la classe Plotter salva i grafici da lei generati, quindi viene usata qui per recuperare i file generati dai Plotter e restituirli.

Parte 3 – Front-end (Javascript/HTML)

La fase finale dell’applicazione sta nella presentazione dei grafici generati. Questo esempio è ridotto all’osso e funzionale più che graficamente perfetto: la presentazione può essere personalizzata modificando i template e i fogli di stile ma ciò non fa parte di questo articolo.

I due file principali sono /templates/index.html e /static/ref.js. Il primo è un semplicissimo file html che include il secondo al suo interno:

<!DOCTYPE html>
<html>
<head>
    <title>Antima.it Plotter</title>
    <script src="{{url_for('.static', filename='ref.js')}}"></script>
</head>
<body>
</body>
</html>

Il secondo contiene le funzioni che interagiscono con il nostro programma python:

document.addEventListener('DOMContentLoaded', () => { reload(); });
setInterval(function() { reload(); }, 6000);

function reload() {
    let req = new XMLHttpRequest();
    req.onreadystatechange = function() {
        if(this.readyState === 4 && this.status === 200) {
            let plots = JSON.parse(req.responseText).plots;
            document.body.innerHTML = "";
            plots.forEach(function(plot) {
                let img = document.createElement("img");
                img.src = "/static/plots/"+plot+"?"+ new Date().getTime();
                document.body.appendChild(img);
                document.body.appendChild(document.createElement("br"));
            });
        }
    }
    req.open("GET", "/plots", true);
    req.send();
}

Le prime due righe hanno il compito, rispettivamente, di far sì che la funzione reload venga chiamata quando la pagina finisce di caricare e di chiamarla nuovamente ad intervalli di 6000 ms (o 6 secondi).

La funzione reload altro non esegue se non l’inoltro di una richiesta HTTP get che verrà ricevuta dall’endpoint /plots definito in app.py. Alla ricezione della risposta, se con esito positivo, procederà a cambiare il contenuto del tag <body> della pagina con una serie di tag <img> che richiameranno i grafici generati. 

img.src = "/static/plots/"+plot+"?"+ new Date().getTime();

aggiunge un timestamp all’URI del grafico ed è un piccolo “trucco” che viene utilizzato per ingannare il browser facendogli pensare che l’immagine sia diversa da quella precedente, in maniera tale da non far caricare la versione in cache (precedente, non aggiornata).

Quanto appena discusso, assieme a tutti i file ed alle istruzioni per l’installazione automatica del mini-progetto, è reperibile tramite la seguente repository nel nostro account github.  

A questo punto è possibile fare partire l’applicazione lanciando l’app flask:

python3 app.py

e testare il tutto attaccando un Arduino con uno sketch che invii dati via seriale (per testare potrebbe andare bene quello proposto nel primo articolo della serie, che stampa valori della funzione seno nel tempo) e apremdo il browser all’indirizzo http://localhost:5000.

 

Qui sopra sono riportati i risultati dell’esecuzione utilizzando come schede un Elegoo Mega ed un Arduino Nano Sense 33 BLE con due sketch di test.

Piccola nota in chiusura: bisogna sempre ricordare che eseguire un’app flask in questo modo è poco sicuro e che il server di sviluppo di default non è uno strumento adatto a situazioni professionali. 

Nel caso in cui ci fossero dubbi o passaggi poco chiari, sentitevi liberi di lasciare un commento nella sezione qui sotto, o di inviare un messaggio o una mail tramite la pagina dei contatti. Nel prossimo articolo verranno trattati dei casi d’uso più avanzati della libreria, aprendo anche a futuri sviluppi della libreria da parte di noi di antima.it.

Acquisizione ed elaborazione dati con Arduino – Casi d’uso avanzati della libreria antimait →

Scrivi un commento

Commento