Dopo aver configurato i Sonoff con Tasmota, è ora di cominciare a scrivere qualche riga di codice per la gestione dei dispositivi.
Ci sono fondamentalmente due modi di interagire con i dispositivi: chiamarli dal programma che fa da console oppure scrivere un piccolo demone costantemente in ascolto sui messaggi di un broker MQTT che aggiorni lo stato dei device in un database SQL.
Per questo esercizio ho scelto la seconda via.
Dal momento che lo scopo di questo progetto è l’apprendimento, ho scelto di usare Python per scrivere il piccolo demone perché volevo migliorare le mie conoscenze del linguaggio. La parte di codice in Python non sarà quindi eccelsa, me ne scuso fin d’ora.
Tralasciato di spiegare la parte di installazione di un broker MQTT, in quanto la procedura è molto ben documentata in molti posti. In seguito potrei dedicare un articolo a MQTT.
Nella mia configurazione ho installato Mosquitto sul server Linux casalingo. Chi non dispone di un server Linux può tranquillamente installare Mosquitto su un Raspberry Pi. La configurazione utilizzata è abbastanza simile al default, l’unica differenza sostanziale è che ho abilitato l’autenticazione con utente e password in quanto prevedo, in seguito, di esporre il broker a Internet.
Il server MQTT si chiama broker, prendete nota.
Iniziamo a creare un database MySQL minimale con due tabelle master/detail. La prima tabella device
ha questa struttura:
CREATE TABLE device (
iddevice int(10) unsigned NOT NULL AUTO_INCREMENT,
device varchar(255) NOT NULL,
mqtt_topic varchar(255) NOT NULL,
PRIMARY KEY (iddevice)
)
la tabella dettaglio devicestatus
con gli stati dei device è questa:
CREATE TABLE devicestatus (
iddevicestatus int(10) unsigned NOT NULL AUTO_INCREMENT,
iddevice int(10) unsigned NOT NULL,
status varchar(255) NOT NULL,
value varchar(255) NOT NULL,
stamp datetime NOT NULL,
PRIMARY KEY (iddevicestatus)
)
Ora che abbiamo le strutture dati, è ora di scrivere il codice.
La procedura, per nulla ottimizzata, dovrà iscriversi a tutti i messaggi in arrivo sul broker MQTT e, ad ogni messaggio, verificare se il topic è tra quelli configurati. In caso positivo viene aggiornato lo stato del device nel database.
Questo algoritmo non ha problemi con un numero limitato di device o di messaggi, ma non scala benissimo. Per centinaia o migliaia di device gestiti e di messaggi al minuto è necessario un algoritmo più ottimizzato, ma la cosa va oltre lo scopo dell’articolo.
La procedura in python che segue fa uso di paho-mqtt per l’interfaccia verso il broker MQTT, questo è il codice:
import os import sys import time import ConfigParser import mysql.connector as mariadb import paho.mqtt.client as mqtt ## leggo la configurazione config = ConfigParser.ConfigParser() config.read('config.ini') ## apro la connessione con il database db = mariadb.connect(host=config.get('sql','host'), user=config.get('sql','user'), password=config.get('sql','password'), database=config.get('sql','database')) ## per evitare il caching delle query db.autocommit = True dbcur = db.cursor() ## callback per la connessione def mqtt_connect(client, userdata, flags, rc): client.subscribe("#") ## callback per la ricezione dei messaggi def mqtt_message(client, userdata, message): dbcur.execute("SELECT mqtt_topic,iddevice FROM device") for row in dbcur.fetchall(): if row[0] in message.topic: dbcur.execute("DELETE FROM devicestatus WHERE iddevice={} AND status='{}'".format(row[1],message.topic)) dbcur.execute("INSERT INTO devicestatus SET iddevice={},status='{}',value='{}',stamp=NOW()".format(row[1],message.topic,message.payload)) ## Procedura principale mqtt_client = mqtt.Client() mqtt_client.username_pw_set(config.get('mosquitto', 'user'), config.get('mosquitto', 'password')) ## definisco i callback mqtt_client.on_connect = mqtt_connect mqtt_client.on_message = mqtt_message ## apro la connessione con il broker mqtt mqtt_client.connect(config.get('mosquitto', 'host'), 1883, 60) ## e rimango in attesa fino allo stop del programma mqtt_client.loop_forever()
Come si vede, i dati di configurazione sono registrati un un file INI esterno con questa struttura
[sql] host = localhost database = nomedatabase user = nomeutentesql password = segreta [mosquitto] user = utente password = segreta host = localhost
Questo permette di condividere la configurazione dei server SQL e MQTT tra la procedura di ascolto e quella di interfaccia e di non registrare delle credenziali nel caso in cui si utilizzino dei repository.
La struttura del programma è abbastanza semplice.
Dopo l’importazione delle librerie utili al programma viene letto il file INI nella variabile config
.
Quindi viene aperta la connessione a SQL e viene impostato autocommit
per evitare che Python metta in cache i risultati delle query e, quindi, non legga eventuali modifiche alla tabella device
.
Subito dopo ci sono le due funzioni di callback. In pratica, una volta terminata l’inizializzazione, il programma rimane in ascolto di nuovi messaggi tramite loop_forever()
nell’ultima riga. Quando arrivano dei messaggi viene chiamata la funzione mqtt_message
che si prende cura di elaborare il messaggio.
Questo ricorda lontanamente i primi programmi in MSC di Windows 3.0 con 200 punti e virgola per un “Hello World”, ma non divaghiamo
La funzione mqtt_connect
viene chiamata all’avvenuta connessione al broker ed esegue immediatamente l’iscrizione a tutti i topic, il carattere #
è il jolly che indica questo.
Dentro mqtt_message
si trova la parte attiva del programma: una volta arrivato il messaggio, viene eseguita una SELECT
che scansiona tutti i topic di interesse, se il messaggio contiene il topic, viene (ri)creato un record in devicestatus
con il contenuto del messaggio.
Dopo la definizione delle funzioni di callback, si passa alla parte di inizializzazione della connessione MQTT e la chiamata al loop infinito di attesa dei messaggi.
Come si può vedere, in poche righe è possibile scrivere una piccola procedura che rimane in ascolto e registra su in database SQL lo stato degli oggetti MQTT.
Questo piccolo demone ha solamente funzioni di ascolto/aggiornamento, la parte di invio dei comandi per modificare lo stato degli attuatori e di visualizzazione degli stati è demandata ad un’altra procedura, che costituisce la parte attiva con interfaccia verso l’utente.
Lascia un commento