I DATI IN TEMPO REALE

Visualizzare i dati di consumo (e temperatura) su un client in tempo reale

Per visualizzare i dati raccolti dal broker ho utilizzato una schermata di pygame già esistente nel mio Raspilight(la scelta è obbligata perché è una GI che permette di visualizzare facilmente al suo interno finestre web che mi servono per altri scopi).
Attraverso uno script in python mi collego al broker e scarico i dati dei topic sulla finestra in pygame. Ottengo così la visualizzazione della:
- temperatura cabina caldaia (che controlla la temperatura all’interno di detta cabina perché non scenda sotto zero)
- la temperatura esterna (che ho chiamato North per distinguerla da altri sensori eventuali)
- il contatore giornaliero del gas.
Utilizzo, come protocollo, Json per impacchettare i dati e farli inviare da MQTT. Come da informazioni sul web, Json è leggero, affidabile, permette l’invio di dati numerici ed è utile in caso di invio contemporaneo di più dati insieme.
Qui di seguito la parte essenziale del codice:

broker = "192.168.xxx.xxx"
temp1=''
temp2=''
gas=''
time_pub=''
alerts=''

def omessage(client, userdata, message):

if message.topic=="Temps/ExternalNorth":
global temp1
json_Data=json.loads(message.payload.decode("utf-8"))#+ " ExternalNorth ")
SensorID = json_Data['Sensor_ID']
Date = json_Data['Date']
temp1=json_Data["T_ExtNorth"]

if message.topic=="Temps/Boiler":

global temp2
json_Data=json.loads(message.payload.decode("utf-8"))
temp2=json_Data["Temp_Boiler"]
#temp2=(str(message.payload.decode("utf-8")+" Boiler"))

if message.topic=="Counters/Gas":

global gas
json_Data=json.loads(message.payload.decode("utf-8"))
gas=json_Data["Gas_counter"]

if message.topic=="Time":

global time_pub
time_pub=(str(message.payload.decode("utf-8")))

if message.topic=="Temps/Alerts":

global alerts
alerts=(str(message.payload.decode("utf-8")))

#funzione per la disconnessione mentre è running

def on_disconnect(client, userdada, rc):

if rc!=0:

global temp2
temp2=str("stopped")
global temp1
temp1=str("stopped")

client=paho.Client("cliente-001")

#con try, in caso di server off il ciclo prosegue e riporta errore su GUI

try:
client.connect(broker)
except:
print("failed, server is not reachable")

client.on_disconnect=on_disconnect
client.on_message=omessage
client.loop_start()
client.subscribe("Counters/#")
client.subscribe("Temps/#")
client.subscribe("Time")
time.sleep(0.5)

Nel ciclo principale, successivo al blocco sopra, si vede, all’inizio, un controllo da me ideato per ricevere un avviso nel caso di blocco del publisher.

Se il broker si spegne o cade c’è già un avviso di MQTT che gestisce l’errore: on_disconnect.

C'è poi una funzione, "keepalive" che controlla che un client sia attivo (a livello di connessione TCP) e, se cade, il broker manda un messaggio agli altri client con scritto quello che si vuole (my last will). Inoltre nel mio caso con un publisher ed un broker insieme, questa funzione non ha senso.

Se però a bloccarsi è il publisher a livello di programma, pur rimanendo attiva la connessione, questo non viene rilevato da MQTT che continua a pubblicare l’ultimo topic. Per ovviare a ciò, ho creato un topic, “time_pub”, che pubblica il Time del publisher, in modo da tenere una "sincronizzazione".
Nel ciclo riportato più sotto rilevo nel subscriber il tempo time_now (togliendo secondi e microsecondi) e, sulla base di questo, identifico altre due variabili, time_now_p1 e time_now_m1, rispettivamente il tempo più un minuto e meno un minuto.
Se il tempo del publisher è uguale al tempo del subscriber o lo stesso più un minuto o meno un minuto, procedo con il programma. La scelta di utilizzare un intervallo è dettata da esperienze che hanno mostrato che può esserci uno sfasamento di alcuni secondi che creerebbe falsi errori.
In caso di differenza, il programma prima riprova la connessione e se fallisce nuovamente da errore sullo schermo.

C’è poi un ulteriore controllo: se il topic “alerts” non è ok. L’alert è relativo alla temperatura nel box caldaia che se va sotto zero (pericolo di rottura della caldaia) mi manda una mail.
Oltre alla mail compare anche nel topic un messaggio che viene quindi visualizzato sullo schermo. Questo if, visibile sotto, fa dunque questo lavoro.

In realtà il messaggio sarà di complemento ad una funzione che vorrei realizzare in uno dei due modi seguenti. Potrei mettere una elettrovalvola su un tubo dell’acqua sanitaria che, una volta rilevata la temperatura a rischio gelo, aprirà l’acqua calda per mettere in moto la caldaia.
Questa soluzione però deve essere a prova di errore perché c’è il rischio che se qualcosa non funziona rimane aperta l’elettrovalvola.
Un’altra soluzione è quella di utilizzare IFTTT e innescare un riscaldamento in una stanza in caso di temperatura esterna pericolosamente bassa. Ne parlo a parte.


Ecco il ciclo while:

while True:

time_now=datetime.datetime.now()
time_now=time_now.replace(second=0,microsecond=0)
time_now_p1=time_now +datetime.timedelta(0,60)
time_now_p1=time_now_p1.strftime("%H:%M")
time_now_m1=time_now -datetime.timedelta(0,60)
time_now_m1=time_now_m1.strftime("%H:%M")
time_now=time_now.strftime("%H:%M")

for event in pygame.event.get():

if event.type == pygame.QUIT:

pygame.quit()
sys.exit()

if time_now==time_pub or time_now_p1==time_pub or time_now_m1==time_pub:

if alerts != "OK":

screen.blit(background, (0,0))
text1=font.render(" %s" %temp1, True, (0,255,0))
screen.blit(text1, (50,20))
text2=font.render(" %s" %temp2, True, (0,255,0))
screen.blit(text2, (50,100))
text3=font.render(" %s" %alerts, True, (0,255,0))
screen.blit(text3, (50,200))
time.sleep(0.2)
pygame.display.update()

elif alerts == "OK":

screen.blit(background, (0,0))
text1=font.render(" %s T External North" %temp1, True, (0,255,0))
screen.blit(text1, (50,20))
text2=font.render(" %s Temperature Boiler" %temp2, True, (0,255,0))
screen.blit(text2, (50,100))
text4=font.render(" %s m3 Gas Counter" %gas, True, (0,255,0))
screen.blit(text4, (50, 200))
time.sleep(0.2)
pygame.display.update()

#se il time del client è più o meno un minuto il time del pub
#prova a connettersi e se non riesce mostra errore

else:

try:

client.connect(broker)
client.on_message=omessage
client.loop_start()
client.subscribe("Temps/#")
client.subscribe("Time")
client.subscribe("Counters/#")
temp1=str("Something is wrong")
temp2=str("try the restart")
gas=str("wait!")
screen.blit(background, (0,0))
text1=font.render(" %s " %temp1, True, (0,0,255))
screen.blit(text1, (50,20))
text2=font.render(" %s " %temp2, True, (0,0,255))
screen.blit(text2, (50,100))
text4=font.render(" %s " %gas, True, (0,0,255))
screen.blit(text4, (50, 200))
time.sleep(1)
pygame.display.update()

except:

temp1=str("no connection")
temp2=str("server is off")
gas=str("damn!!")
screen.blit(background, (0,0))
text1=font.render(" %s" %temp1, True, (255,0,0))
screen.blit(text1, (50,20))
text2=font.render(" %s" %temp2, True, (255,0,0))
screen.blit(text2, (50,100))
text4=font.render(" %s" %gas, True, (0,255,0))
screen.blit(text4, (50, 200))
time.sleep(0.1)
pygame.display.update()

pygame.quit()
sys.exit()


Nell'immagine sotto vedete uno screenshot del monitor che presenta questi dati (incluso un grafico ad anello per il consumo giornaliero). La temperatura sala non è collegata perchè si tratta del raspi che uso per il test.