Dieser Artikel ist die Fortsetzung zu dem Artikel Solarregler Datalogger. Dort wird beschrieben, wie man HW-mäßig den DeltaSol BS Plus Regler ausliest und die Daten in einer Round Robin Datenbank (RRDTools) speichert.
Das Ziel
Um die Daten des Solarreglers auch anderen Systemen zur Verfügung zu stellen (z.B. einem Hausbussystem) sollen die Daten auch über einen MQTT-Broker zur Verfügung gestellt werden. Außerdem bietet es sich dann auch gleich an, die Speicherung und Visualisierung der Daten auf eine Influx-Datenbank mit Grafana-Frontend umzustellen.
Umsetzung
MQTT ist ein Protokoll Standard, der für die Kommunikation zwischen Maschinen entwickelt wurde. Einzelheiten zum MQTT-Standard dazu findet man z.B. auf Wikipedia.
Für die Kommunikation über MQTT braucht man einen Broker, der die Verteilung der Nachrichten an die MQTT-Clients übernimmt. Als Broker wird dabei der Mosquitto-Broker verwendet, der auch auf dem Beaglebone läuft.
Das Python-Script zur Dekodierung der vBus-Nachrichten wird jetzt so erweitert, dass es gleichzeitig die Daten als MQTT-Client an den MQTT-Broker abliefert.
Mit einem zweiten Python-Script werden die Daten dann vom MQTT-Broker abgeholt und in eine Influx-Datenbank geschrieben, die auf einem RaspberryPi 3 läuft. Dieses zweite Script läuft ebenfalls auf dem BeagleBone. Nur die Influx-DB habe ich ausgelagert aus Performance-Gründen ausgelagert.
Installation Mosquitto-Broker
Installation von mosquitto als MQTT-Broker: sudo apt-get install mosquitto
Passwort für MQTT-Client anlegen: sudo mosquitto_passwd -b pass.txt user passwort
In der mosquitto.conf mit nano /etc/mosquitto/mosquitto.conf die folgenden Zeilen einfügen: allow_anonymous false password_file /etc/mosquitto/pass.txt
Neustart des Mosquitto-Brokers mit: sudo systctl restart mosquitto
Solarregler Datalogger mit MQTT-Client
Für die Kommunikation mit dem MQTT-Broker wird die Paho-MQTT Python-Library benutzt. Die Installation der Lib erfolgt durch pip install paho-mqtt.
Das Pythonscript selbst wird wieder unter /home/debian/solarregler mit nano /home/debian/solarregler/resol_logger.py angelegt bzw. bearbeitet. Das Script ist eine erweitere Version des Script aus dem ersten Artikel, d.h. auch dieses Script legt auch noch eine RRD-Datenbank an und schreibt die Daten dort hin.
#!/usr/bin/python# Script for logging RESOL DeltaBS Plus to a RRDTOOL-Databank# with integrated MQTT-Client# Thomas Wedemeyer 2018# www.thomas-wedemeyer.de# Remark: My system is using the System schemata 9 -> WMZ is not testetimportserialimportsysimportrrdtoolimportosimporttimeimportpaho.mqtt.clientasmqtttemperatur_list=[0.0forxinrange(4)]drehzahl1=0drehzahl2=0relmask=0errormask=0relTime1=0relTime2=0#wmz = 0 # remove comment tag for WMZ-Supportmqtt_connected=Falsedefvbus_decode(rxd_list,byte_counter):globaltemperatur_list,drehzahl1,drehzahl2,relmask,errormask,relTime1,relTime2#,wmz#Check commandifrxd_list[5]==0x10:if((rxd_list[1]!=0x10)|(rxd_list[2]!=0x0)):returnFalsesystem=((rxd_list[4]<<8)|rxd_list[3])print"Systemcode: ",hex(system)datasets=rxd_list[8]#get the number of datasetsprint"Dataset=",datasets#copy the sepetts back to the msb of the bytes forxinrange(0,datasets-1,1):forbcntinrange(0,4,1):if(rxd_list[14+x*6]&(1<<bcnt))>0:rxd_list[10+bcnt+x*6]=rxd_list[10+bcnt+x*6]|0x80#crc is only for whiny people... so no crc checking here#stip protocol information of the sepetts & crcprotocolOffset=0foriinrange(0,datasets-1,1):forbytecntinrange(0,4,1):rxd_list[bytecnt+4*i]=rxd_list[10+bytecnt+4*i+protocolOffset]protocolOffset=protocolOffset+2ifsystem==0x4221:#Delta BS Plus V1ifdatasets!=7:returnFalse#decoding of the message block according to VBusSpecificationResol.xml for Resol Delta BS Plustemperatur_list[0]=((rxd_list[1]<<8)|rxd_list[0])/10.0print"Temperatur 0 =",temperatur_list[0]temperatur_list[1]=((rxd_list[3]<<8)|rxd_list[2])/10.0print"Temperatur 1 =",temperatur_list[1]temperatur_list[2]=((rxd_list[5]<<8)|rxd_list[4])/10.0print"Temperatur 2 =",temperatur_list[2]temperatur_list[3]=((rxd_list[7]<<8)|rxd_list[6])/10.0print"Temperatur 3 =",temperatur_list[3]forxinrange(0,4,1):# convert to signed temperaturiftemperatur_list[x]>3276.0:temperatur_list[x]=temperatur_list[x]-6553.4drehzahl1=rxd_list[8]print"Drehzahl 1=",drehzahl1drehzahl2=rxd_list[9]print"Drehzahl 2=",drehzahl2relmask=rxd_list[10]print"Relmask=",hex(relmask)errormask=rxd_list[11]print"Errormask=",hex(errormask)time=((rxd_list[13]<<8)|rxd_list[12])print"Time =",time/60,":",time%60schema=rxd_list[14]print"Schema=",schemarelTime1=((rxd_list[17]<<8)|rxd_list[16])print"RelTime1 =",relTime1relTime2=((rxd_list[19]<<8)|rxd_list[18])print"RelTime2 =",relTime2#wmz= ((rxd_list[21]<<8) | rxd_list[20]) + (((rxd_list[23]<<8) | rxd_list[22])*1000) + (((rxd_list[25]<<8) | rxd_list[24])*1000000)#print "WMZ = ",wmzreturnTrueelifsystem==0x427b:# DeltaBSPlus V2ifdatasets!=9:returnFalse#decoding of the message block according to VBusSpecificationResol.xml for Resol Delta BSPlustemperatur_list[0]=((rxd_list[1]<<8)|rxd_list[0])/10.0print"Temperatur 0 =",temperatur_list[0]temperatur_list[1]=((rxd_list[3]<<8)|rxd_list[2])/10.0print"Temperatur 1 =",temperatur_list[1]temperatur_list[2]=((rxd_list[5]<<8)|rxd_list[4])/10.0print"Temperatur 2 =",temperatur_list[2]temperatur_list[3]=((rxd_list[7]<<8)|rxd_list[6])/10.0print"Temperatur 3 =",temperatur_list[3]forxinrange(0,4,1):# convert to signed temperaturiftemperatur_list[x]>3276.0:temperatur_list[x]=temperatur_list[x]-6553.4drehzahl1=rxd_list[8]print"Drehzahl 1=",drehzahl1drehzahl2=rxd_list[12]print"Drehzahl 2=",drehzahl2relmask=rxd_list[10]print"Relmask=",hex(relmask)errormask=((rxd_list[21]<<8)|rxd_list[20])print"Errormask=",hex(errormask)time=((rxd_list[23]<<8)|rxd_list[22])print"Time =",time/60,":",time%60schema=rxd_list[17]print"Schema=",schemarelTime1=((rxd_list[11]<<8)|rxd_list[10])print"RelTime1 =",relTime1relTime2=((rxd_list[15]<<8)|rxd_list[14])print"RelTime2 =",relTime2#wmz= ((rxd_list[31]<<24) | (rxd_list[30]<<16) |(rxd_list[29]<<8) | rxd_list[28])*1000) + (((rxd_list[25]<<8) | rxd_list[24])*1000000)#print "WMZ = ",wmzreturnTrueelse:print"System not supported"returnTrueelse:returnFalsedefcheck_database():ifos.path.isfile("%s/resol_databank.rrd"%(os.path.dirname(os.path.abspath(__file__))))==False:print"Database not found -> Create Database"rrdtool.create("%s/resol_databank.rrd"%(os.path.dirname(os.path.abspath(__file__))),"--start","now","--step","300",# 300sec -> 5Min"--no-overwrite",# don't overwrite old database"DS:Temp_Collector:GAUGE:600:-25:150",# 600 sek -> 10Min Haertbeat, Min:-25, Max 150"DS:Temp_TankBottom:GAUGE:600:-25:150","DS:Temp_TankTop:GAUGE:600:-25:150","DS:Temp_Recirculation:GAUGE:600:-25:150","DS:PumpSpeed:GAUGE:600:0:100",# 600 Sec Heartbeat, Min:0, Max: 100%"DS:Valve:GAUGE:600:0:100","DS:Error:GAUGE:600:0:255","DS:PumpActiv:COUNTER:600:0:99999","DS:ValveActiv:COUNTER:600:0:99999",#DS:Wmz:COUNTER:600:0:99999999"RRA:AVERAGE:0.5:1:2016",# Average 1 point, 2016 points = 7 days / 5min interval"RRA:AVERAGE:0.5:12:8760",# Average 12 points (1hour), 8760 Points = 1 Year (24*365)"RRA:MIN:0.5:288:3650",#Min 288 points (1Day), 3650 Points = 10 Years"RRA:MAX:0.5:288:3650","RRA:LAST:0.5:288:3600")else:print"Database allready exits"defupdate_database():globaltemperatur_list,drehzahl1,drehzahl2,relmask,errormask,relTime1,relTime2# build data pointdata='N:'foriinrange(4):data+=str(temperatur_list[i])data+=':'data+=str(drehzahl1)data+=':'data+=str(drehzahl2)data+=':'data+=str(errormask)data+=':'data+=str(relTime1)data+=':'data+=str(relTime2)#data +=':'#data += str(wmz)printdata# insert data into round-robin-databaserrdtool.update("%s/resol_databank.rrd"%(os.path.dirname(os.path.abspath(__file__))),data)defon_connect(client,userdata,flags,rc):globalmqtt_connectedprint("Connected to MQTT-Broker with result code "+str(rc))mqtt_connected=Truedefmain():#MQTT Connection...user="your_userid"passw="your_passwd"client=mqtt.Client()client.on_connect=on_connectclient.username_pw_set(user,passw)client.connect("127.0.0.1",1883,60)# localhost or external ip adr.client.loop_start()check_database()ser=serial.Serial(port="/dev/ttyO2",baudrate=9600)ser.close()ser.open()rxd_list=[0forxinrange(70)]#define a buffer for the receivercounter=0Starttime=time.time()ifser.isOpen():print"Serial is open!"try:whileTrue:value=0data=ser.read()value=ord(data)#print "Value: " , hex(value) ifvalue==0xaa:#print "New Frame"if(counter>0):ifvbus_decode(rxd_list,counter)==True:update_database()ser.close()ifmqtt_connected==True:client.publish("solaranlage/collector",temperatur_list[0])client.publish("solaranlage/tank_top",temperatur_list[2])client.publish("solaranlage/tank_bottom",temperatur_list[1])client.publish("solaranlage/recirculation",temperatur_list[3])client.publish("solaranlage/pumpspeed",drehzahl1)client.publish("solaranlage/valve",drehzahl2)client.publish("solaranlage/error",errormask)client.publish("solaranlage/pumpactiv",relTime1)client.publish("solaranlage/relactiv",relTime2)client.loop_stop()#Stop loop client.disconnect()# disconnectexit()counter=0rxd_list[counter]=valueif(counter<59):counter+=1if(time.time()>(Starttime+60)):print"Timeout!"ser.close()exit()exceptKeyboardInterrupt:ser.close()if__name__=="__main__":main()
Diese Script wird jetzt alle 5 Minuten über einen Cronjob aufgerufen und liefert die Daten beim MQTT-Broker ab.
MQTT-Client zum Befüllen der Influx-Datenbank
Für die Kommunikation mit der Influx-Datenbank gibt es natürlich wieder eine Python-Library, die vor der Benutzung installiert werden muss: pip install influxdb
Das Script zum Befüllen der Influx-Datenbank wird jetzt im Verzeichnis “/home/debian/MQTT-Influx” angelegt: mkdir /home/debian/MQTT-Influx nano /home/debian/MQTT-Influx/mqtt2influx.py
#!/usr/bin/env python3importpaho.mqtt.clientasmqttimportdatetimeimporttimefrominfluxdbimportInfluxDBClientdefon_connect(client,userdata,flags,rc):print("Connected with result code "+str(rc))client.subscribe("#")defon_message(client,userdata,msg):print("Received a message on topic: "+msg.topic)# Use utc as timestampreceiveTime=datetime.datetime.utcnow()message=msg.payload.decode("utf-8")isfloatValue=Falsetry:# Convert the string to a float so that it is stored as a number and not a string in the databaseval=float(message)isfloatValue=Trueexcept:print("Could not convert "+message+" to a float value")isfloatValue=FalseifisfloatValue:print(str(receiveTime)+": "+msg.topic+" "+str(val))json_body=[{"measurement":msg.topic,"time":receiveTime,"fields":{"value":val}}]dbclient.write_points(json_body)print("Finished writing to InfluxDB")# Set up a client for InfluxDBdbclient=InfluxDBClient('IP_of_InfluxDB',8086,'user_id','pass_id','database_name')# Initialize the MQTT client that should connect to the Mosquitto brokerclient=mqtt.Client()client.on_connect=on_connectclient.on_message=on_messageconnOK=Falsewhile(connOK==False):try:#MQTT Connection...user="your_mqtt_userid"passw="your_mqtt_pw"client.username_pw_set(user,passw)client.connect("l27.0.0.1",1883,60)connOK=Trueexcept:connOK=Falsetime.sleep(2)# Blocking loop to the Mosquitto brokerclient.loop_forever()
Diese Script wird jetzt wieder mit chmod a+x mqtt2influx.py ausführbar gemacht.
Normalerweise läuft dieses Script die ganze Zeit, wenn es einmal gestartet wurde und überträgt jetzt jede Zustandsänderung auf dem MQTT-Broker mit einem Zeitstempel an die Influx-Datenbank. Da die Welt nicht perfekt ist, kann es aber immer mal passieren, das z.B. ein Timeout bei einer Connection auftritt. In diesem Fall wird das Script mit einem Fehler beendet. Um dieses Problem zu umgehen, wird die Ausführung des Python-Scripts alle 5 Minuten durch ein Bash-Script überwacht. Wenn das Python-Script nicht in der Taskliste gefunden wird, wird das Script automatisch neu gestartet.
Grafana bei Systemstart hochfahren: sudo /bin/systemctl enable grafana-server
Manueller Start von Grafana mit: sudo /bin/systemctl start grafana-server
Jetzt kann man die Grafana-Oberfläche im Webbrowser mit “http:\your_rasppi_ip:3000” öffnen. Das Default Login ist admin mit dem Passwort admin .
Grafana mit Influx verbinden
Unter Configuration->Datasource-> Add data source anklicken
InfluxDB wählen
Name z.B. MyInfluxDB
Bei HTTP: http://localhost:8086 eintragen
Bei InfluxDB Details “your database”, user, password und HTTP Method “GET” eintragen
Speichern nicht vergessen…
Erstes Dasboard anlegen
Bei dem “+"-Zeichen -> Create Dashboard
Add Query
Query = MyInfluxDB
From: default
select measurement anklicken und Topics aus der DB wählen z.B. /solaranlage/collector
Visualisierung anpassen usw.
Und fertig….
Bei mir sieht das Dashboard dann am Ende so aus:
Ergebnis
So, wie man mal wieder sieht ist es relativ einfach aus einem kleinen Problem ein viel größeres zu machen ohne, dass man wirklich viel erreicht hat. 😏 Aber schön anzusehen ist die neue Darstellung der Temperaturverläufe auf jeden Fall!
Aber im Ernst: Die Anbindung der Solaranlage über MQTT ist nicht nur für die Influx Anbindung interessant. Es ergeben sich dadurch noch viel mehr Möglichkeiten z.B. aus die dem Bereich der Hausautomatisierung. Ich habe inzwischen die Solaranlage über MQTT mit in den Homeassistant mit eingebunden, wo sich wiederum weitere Möglichkeiten ergeben… Dazu aber später mehr!
Auch die Möglichkeiten, die die Kombination aus Influx und Grafana bieten sind wirklich spannend!