L’unità centrale (UC) è costituita da un Raspberry Pi 4 B+, sul
quale gira un software in-house (Audace Mothics lite) scritto in
Python, un broker MQTT e un server Flask.
I compiti dell’UC sono
- acquisizione dati dalle unità remote mediante protocollo MQTT, seriale (e TCP/IP)
- aggregazione e processing dei dati dai sensori
- hosting di un server locale con Flask per visualizzare i dati acquisiti
- hosting di un broker MQTT
- access point Wi-Fi per la connessione delle unità remote.
Software
Ecco alcune note, riflessioni e opinioni sulla struttura del
progetto. Tutti i riferimenti ad Aggregator, WebApp e API sono
nella sezione relativa all’analisi post-mortem e a Mothics.
Struttura generale
Classi di comunicazione:
Communicator: classe che aggrega i vari protocolli di comunicazione usati, interroga e.g. porta seriale e broker MQTT, aggrega i dati ‘raw’ (in un dizionario, con i topics come chiavi e liste di dizionari{timestamp: dato}come valori); fornisce un loop principale che itera su tutti i protocolli di comunicazione fornitiBaseInterface: ABC per le classi specifiche di comunicazioneMQTTInterface,SerialInterface
Classe di aggregazione dati:
Aggregator: classe che campiona a intervalli regolari e aggrega tutti i dati ‘raw’ forniti daCommunicator, ne permette una prima analisi dati e li immagazzina in dataclasses apposite che ne permettono una manipolazione, salvataggio e conversione più approfondita.
Dataclasses:
DataPoint: singolo punto dati generato daAggregatora intervalli regolariTrack: collezione diDataPoint(ed eventuali metadati) che permette salvataggio su file, conversione e manipolazione dei dati; contiene i dati da visualizzare sulla pagina intranet e da preservare su un eventuale database esterno
Server Flask:
WebApp: classe che avvia e fornisce un server Flask in maniera non bloccante, capace di acquisire dati da unTracke da dizionari (e.g. stato delle UR) attraverso funzioni getter
Communicator
Numerazione delle unità remote
Ai fini della comunicazione con l’UC, ogni unità remota ha un indirizzo ben preciso
rm<numero>/<sensore>/<grandezza>
ad esempio, i dati sulla longitudine sono comunicati dall’UR 1, e sono presi dal sensore GPS; dunque, l’indirizzo sarà
rm1/gps/long
Attualmente, la numerazione delle unità remote esistenti o in sviluppo è
- rm1: GPS-IMU
- rm2: anemometro
Le grandezze fisiche di interesse sono, per le varie unità remote
- gps/lat: latitudine
- gps/long: longitudine
- wind/speed: velocità del vento
- TBD
Protocollo MQTT
Il protocollo MQTT è la scelta primaria per comunicare con le UR (eccetto GPS-IMU, collocate assieme a UC e alimentate dallo stesso pacco batterie).
Quality of Service (QoS)
Il protocollo MQTT fornisce differenti livelli di QoS, i.e. differenti metodi di consegna dei broadcasts e diversi livelli di reliability. In particolare, individuiamo
- QoS 0 (best effort): il protocollo cerca di recapitare i messaggi senza restrizioni di alcun genere; essi potrebbero essere recapitati più volte, o affatto
- QoS 1 (at least once): è garantito il recapito del messaggio almeno una volta, con potenziali duplicati
- QoS 2 (exactly effort): è garantito il recapito del messaggio una e una sola volta, con un handshake fra broker e destinatario per garantire la consegna effettiva del broadcast
Ai nostri scopi, il minimo livello accettabile è QoS 1; QoS 2 è ideale ma potrebbe introdurre ritardi e latenze insostenibili.
Il QoS deve essere indicato e garantito da chi pubblica il messaggio (nel nostro caso, l’UR).
Alcuni riferimenti:
- Mosquitto test broker
- paho-mqtt documentation
- Sending Data over MQTT
- A Beginner’s Guide to MQTT: Understanding MQTT, Mosquitto Broker, and Paho Python MQTT Client
Comunicazione seriale
La comunicazione fra RasPi e Arduino può avvenire anche via
seriale con cavo USB, senza ulteriori complicazioni. Via Python, si
può definire una classe opportuna che sfrutta pyserial, e.g.
SerialInterface, che stia in ascolto di dati dall’Arduino.
Alcuni riferimenti:
Telnet, TCP/IP e Python
Non è taboo implementare anche l’uso del protocollo TCP/IP per comunicare a basso livello con i moduli remoti. I motivi di una scelta simile sono vari,
- sfruttare il lavoro già fatto in questo senso lato Arduino
- avere un metodo di backup di comunicazione in caso di problemi con il broker MQTT, o di eccessiva latenza con invio e ricezione di broadcasts MQTT
Alcuni riferimenti:
Comunicazione UC -> UR
In linea di principio è possibile comunicare con le UR dall’UC per inviare comandi; ciò è triviale con MQTT, semplice con la comunicazione seriale, e fattibile con TCP/IP.
L’unico possibile problema è dal lato delle UR: è necessario un loop bloccante di ascolto per ricevere eventuali messaggi dall’UC, che può introdurre latenze nella comunicazione.
Qualora si scegliesse di implementare la comunicazione 2-way, si propone l’uso dell’indirizzo
rm<numero>/sudo
Aggregator
Qui sono presenti tutte le informazioni relative all’interazione fra
Aggregator e Communicator; i dettagli sulla struttura di
Aggregator sono disponibili nella sezione analisi post-mortem.
AsyncIO: asincronia fra Communicator e Aggregator
Communicator, per quanto concerne il protocollo MQTT (via
MQTTInterface) può sfruttare le funzioni di libreria loop_start e
loop_stop, che forniscono un loop asincrono non bloccante, i.e.
permettono il pinging del broker MQTT e la contemporanea esecuzione di
altri processi, fino a interruzione (con loop_stop) o disconnessione
(con disconnect).
Perciò, è possibile avviare il loop via Communicator e, a intervalli
regolari, campionare i dati chiamando opportuni metodi di Aggregator
senza necessità di usare asyncio.
Tuttavia, l’asincronia può essere implementata se si vuole svolgere
qualche task in quel secondo di attesa fra un campionamento e il
successivo; in tal caso, basta trasformare tutto in funzioni asincrone
e usare await sugli asyncio.sleep.
Un’accortezza adottata per garantire la sincronia dei dati passati da
Communicator a Aggregator è un metodo getter esterno invece di
un dizionario aggiornato in place. Passare il dizionario raw_data
di Communicator nell’inizializzatore di Aggregator non è una
strategia valida: esso non viene aggiornato in place durante il
ciclo non bloccante di raccolta dati di Aggregator. Il getter è
utile per recuperate in runtime il dizionario aggiornato di
Communicator.
Hardware
Ci sono alcuni accorgimenti di cui tenere conto nello sviluppo dell’UC.
Spegnimento e riavvio
Lo spegnimento deve essere sicuro, i.e. equivalente a
- chiudere eventuali processi in corso, ognuno secondo le proprie
direttive di arresto (e.g. interruzione del loop e salvataggio
per
Aggregator, disconnessione diCommunicator, …) sudo shutdown
Staccare il cavo di alimentazione non è un modo sicuro di spegnere l’UC. È necessario utilizzare il bottone di spegnimento, che, alla pressione, avvia uno script che esegue i due compiti indicati sopra. Alternativamente, è presente un comando nell’interfaccia da linea di comando di Mothics.
