in Antima for IoT, Tutorial Python

Acquisizione ed elaborazione dati con Arduino – Introduzione alla libreria antimait

In un precedente articolo si è parlato di acquisizione dati da parte di sensori basati sul dispositivo ESP8266-01 e si è introdotta una metodologia di acquisizione dati tramite HTTP.

A volte, però, ci si trova per motivi di studio, di testing, o in fasi di prima progettazione, a volere approcciarsi ad un particolare sensore tramite una scheda Arduino per osservarne e capirne il funzionamento o per acquisire dati in scenari in cui una trasmissione wireless non è strettamente necessaria.

In questo articolo verrà presentata una libreria Python3 scritta da noi di Antima.it, contenente diversi strumenti per interfacciarsi con i dispositivi precedentemente introdotti che verrà utilizzata per acquisire dati tramite la connessione seriale e generare dei grafici per studiarne le qualità. Verranno poi mostrati alcuni esempi su come utilizzarla per i propri progetti personali.

antimait è una semplice libreria per Python3 (richiede una versione di Python uguale o superiore alla 3.7), scaricabile tramite pip utilizzando:

$ pip3 install antimait

È inoltre possibile consultare il codice sorgente, assieme alla documentazione più dettagliata, andando sul nostro profilo github, navigando sulla pagina relativa a questa repository.

 

Diagramma UML delle classi principali della libreria

Come introdotto dal diagramma delle classi sopra riportato, il principale protagonista della libreria è rappresentato dalla classe Gateway. Tramite un’istanza di questa classe è possibile ottenere un oggetto che è in grado di captare quando un dispositivo viene connesso tramite una porta seriale, catturandone l’output e rendendolo disponibile tramite un meccanismo basato sul pattern Observer.

Questo meccanismo è implementato da due classi della gerarchia sopra introdotta, DataSource e DataReceiver:

  • Un’istanza di una classe che eredita da DataSource rappresenta un oggetto sorgente di dati. La classe astratta CommInterface, da cui ereditano tutte le classi rappresentanti interfacce di comunicazione, a sua volta eredita da DataSource. Ogni volta che una sorgente ha dei dati disponibili, li mette a disposizione tramite il metodo notify, implementando il ruolo di un observable. L’azione legata alla chiamata di notify viene specificata tramite l’argomento action di tipo Comm, un’enumerazione che può avere valore uguale a comm.DATA o Comm.CLOSE, in questo caso specificando che la sorgente sta per chiudersi.

  • Un oggetto DataReceiver ha il ruolo dell’observer ed è preposto a ricevere e trattare i dati provenienti da una sorgente. Questa classe può essere utilizzata come una base per personalizzare le applicazioni utente. Il suo metodo update iene invocato ogni qualvolta una sorgente osservata esegue una chiamata a  notify.

Il meccanismo tramite in cui antimait si interfaccia con le porte seriali è basato sulla libreria pyserial di Python.

Conoscendo il nome della porta seriale di interesse, è possibile creare un oggetto istanza di SerialInterface  e procedere alla gestione dei dati in arrivo creando un DataReceiver personalizzato, attaccandolo all’interfaccia tramite il metodo attach.

La maniera più semplice per approcciarsi all’utilizzo di dispositivi come Arduino è però, come accennato poco fa, utilizzando antmait.Gateway, che permetterà di avere una connessione automatica a più dispositivi. Per raccogliere i dati in arrivo da questi ultimi, bisognerà definire un metodo sull’oggetto Gateway chiamato on_connect.

on_connect rappresenta una routine che verrà invocata ogni qualvolta un dispositivo verrà connesso su una porta seriale ed inizializzato dall’oggetto Gateway, in una maniera simile a come le funzioni di callback vengono utilizzate nella libreria paho.mqtt di cui si era parlato in passato in questo articolo. La firma del metodo da definire è la seguente:

def on_connect(interface: CommInterface, description: str) -> None

In questo modo, si possono creare diversi DataReceiver personalizzati che possono essere attaccati alle interfacce seriali quando queste vengono inizializzate, catturando in automatico i dati ogni volta che vengono prodotti.

Sono disponibili due metodi differenti per avviare una CommInterface:

  • listen, che creerà un nuovo thread in cui attendere dati in entrata, metodo che quindi sarà non bloccante.
  • listen_forever, che avvierà il processo di ascolto dati nello stesso thread in cui verrà eseguito, bloccandolo.

La stessa interfaccia è utilizzata anche da Gateway, per la connessione dei dispositivi. Il metodo close chiuderà l’interfaccia/gateway.

Passiamo ad un piccolo esempio: all’interno del modulo principale di antimait è presente un’implementazione di DataReceiver che effettua una semplice stampa dei dati ricevuti:

class Printer(DataReceiver):
    def update(self, action: Comm, **update: str) -> None:
        if action == Comm.DATA:
            if "data" in update:
                print("Printing data: {}".format(update["data"]))
            else:
                logging.error("data keyword not passed!")
        elif action == Comm.CLOSING:
            logging.info("Printer closing")

L’implementazione del metodo update è abbastanza semplice, se l’azione effettuata è relativa all’arrivo di dati, ci si aspetta la keyword data all’interno del parametro update, altrimenti la chiamata di update è relativa alla chiusura della sorgente.

Utilizzando questa classe, si può procedere con la realizzazione di un piccolo programma che attende dispositivi che si connettano tramite porta seriale e che, sempre tramite essa, inviino i dati letti da sensori. Su piattaforma Linux, per potere utilizzare quanto segue è necessario aggiungere l’utente al gruppo dialout eseguendo:

$ sudo usermod -a -G dialout username

dove username è il nome utente in uso. Una volta fatto ciò, è possibile eseguire il seguente script, che terminerà premendo CTRL + C:

import logging
import antimait

# Settando il liv. di logging a INFO vedremo messaggi
# relativi alla connessione dei dispositivi logging.basicConfig(level=logging.INFO)

gw = antimait.Gateway() printer = antimait.Printer() def on_connect(interface: antimait.CommInterface, description: str) -> None: interface.attach(printer) gw.on_connect = on_connect try: gw.listen_forever() except KeyboardInterrupt: gw.close()

A questo punto, per testare il funzionamento corretto dell’applicazione, si può simulare l’acquisizione di dati tramite un sensore scrivendo un semplice sketch per Arduino che stampi valori relativi ad una funzione discreta nel tempo; qui di seguito del codice che stampa valori della funzione seno ad incrementi di 0.4 unità:

float counter = 0;
void setup() {
    Serial.begin(9600);
}

void loop() {
    Serial.println(sin(counter));
    counter += 0.4;
    delay(1000);
} 

Esempio di output generato su piattaforma Windows 10 utilizzando un Arduino Mega (con lo sketch del seno) ed un Arduino Nano (in cui la funzione è f(t) =t2

Nel prossimo articolo si entrerà più nel dettaglio sull’utilizzo di quanto presentato assieme al modulo di plotting per generare grafici, procedendo poi con il creare una semplice applicazione per visualizzare i grafici generati.

Acquisizione ed elaborazione dati con Arduino – Generazione grafici tramite la libreria matplotlib →

Scrivi un commento

Commento