Piccolo demone per la gestione dei device MQTT

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.

Autore: Luigi Rosa

Consulente IT, sviluppatore, SysAdmin, cazzaro, e, ovviamente, geek.

2 pensieri riguardo “Piccolo demone per la gestione dei device MQTT”

Spazio per un commento

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.