windundsterne / esp-kwb-mqttlogger

ESP Webmos D1 Mini Pro basierter Datenlogger für KWB Easyfire Pelletkessel
13 stars 3 forks source link

Need Help #1

Open kimi1983 opened 2 years ago

kimi1983 commented 2 years ago

Hallo,

im Moment benutzt ich folgenden Script per Serial2Net Adapter.

import serial
from datetime import datetime
import os
import sys
import time
import paho.mqtt.client as mqtt

ser = serial.serial_for_url('socket://192.168.1.71:9946',baudrate=19200)

STATE_WAIT_FOR_HEADER = 1
STATE_READ_MSG = 2
MSG_TYPE_CTRL=1
MSG_TYPE_SENSE=2

LOG_DELTA_TIME = 60        #time interval for logging [s]
LOG_ON_CHANGE = True       #log also when Ctrl-Msg has changed
LOG_RAW_MSG = False        #enable also logging of raw messages
LOG_LAST_RAW_MSG = True    #log also last message before logging current message

DECODE_ALL = False          #True = decode all messages without signal map
DO_DISPLAY = True           #Switch for enabling screen output

aaSignalMaps = [ {} for i in range(255) ]

aaSignalMaps[16] = {
    #Name: Type='b'(bit), Offset, Bit
    #Name: Type='s'(signed)/'u'(unsigned), Offset, Length, Factor, Unit
    'T_Vorlauf_HK1': ('s',5, 2, 0.1,'C'),
    'T_Ruecklauf_Kessel': ('s',7, 2, 0.1,'C'),
    'T_Boiler': ('s',9, 2, 0.1,'C'),
    'T_Kessel':  ('s',11, 2, 0.1,'C'),
    'T_Aussen': ('s',17, 2, 0.1,'C'),
    'T_Rauchgas': ('s',19, 2, 0.1,'C'),
    'T_Steuerung' : ('s',21, 2, 0.1,'C'),
    'T_Stokerkanal' : ('s',29, 2, 0.1,'C')}

aaSignalMaps[17] = {
    #Name: Type='b'(bit), Offset, Bit
    #Name: Type='s'(signed)/'u'(unsigned), Offset, Length, Factor, Unit  
    'Aschebehaelter': ('b',3, 6),
    'Pellet_Vorrat': ('b',4, 3),
    'Brandschutzklappe' : ('b',1,1),
    'Reinigung' : ('b',2,1),
    'Alarm2': ('b',1,2),
    'Alarm1': ('b',1,3),
    'Boiler_Pumpe': ('b',1,5),    
    'HK1_Pumpe': ('b',1,7),
    'Ascheaustragung': ('b', 2,0),
    'Reinigung': ('b',2,1),
    'HK1_Mischer': ('b',2,6),    
    'Hauptrelais': ('b',3,4),
    'Raumaustragung': ('b',3,6),
    'Kessel_AN': ('b',15,0)}

def CrcAdd(nCrc, nByte):
    nCrc = (((nCrc << 1) | nCrc >> 7) & 0xFF)
    nCrc += nByte
    if (nCrc > 255):
        nCrc -= 255 
    return nCrc

class cMessage:
    def __init__(self, nType=0, nLength=0, nID=0, nCounter=0, anData=bytearray(0), nChecksum=0, sTime=0):
        self.sTime=sTime
        self.nType=nType
        self.nLen=nLength
        self.nID=nID
        self.nCounter=nCounter
        self.anData=anData
        self.nChecksum=nChecksum  

    def CopyFrom(self, oMsg):
        self.sTime=oMsg.sTime
        self.nType=oMsg.nType
        self.nLen=oMsg.nLen
        self.nID=oMsg.nID
        self.nCounter=oMsg.nCounter
        self.anData=oMsg.anData
        self.nChecksum=oMsg.nChecksum 

    def GetBit(self, nOffset, nBit):
        nValue = (self.anData[nOffset] >> nBit) & 1
        return nValue

    def GetSignal(self, nOffset, nLen=2, fFactor=0.1, bSigned=False):  
        nValue = 0;
        for nI in range(0,nLen):
            nValue += self.anData[nOffset+nI] << ((nLen-nI-1) * 8)
        if bSigned & (nValue > (1 << (nLen*8-1))):
            nValue -= (1 << (nLen*8))
        fValue = nValue * fFactor
        return fValue

    def Decode(self, aSignalMap):
        aSignalValues = {}
        if (len(aSignalMap)>0):
            for strSignalName, aSig in aSignalMap.items():
                if aSig[0]=='b':
                    #Name: Type, Offset, Bit
                    aSignalValues[strSignalName]=self.GetBit(aSig[1], aSig[2])
                else:
                    #Name: Type, Offset, Length, Factor, Unit
                    aSignalValues[strSignalName]=self.GetSignal(aSig[1], aSig[2], aSig[3], aSig[0]=='s')
        else:
            for nOffset in range(0, self.nLen-6, 1):
                strSignalName = ("Offset_%02d (%03d, %03d)" % (nOffset, self.anData[nOffset] , self.anData[nOffset+1]))
                aSignalValues[strSignalName]=self.GetSignal(nOffset)            
        return  aSignalValues

    def IsCrcOk(self):                  #TODO: does not always work correctly yet ...
        nCrc = 0x02;
        nCrc = CrcAdd(nCrc, self.nLen)
        nCrc = CrcAdd(nCrc, self.nID)
        nCrc = CrcAdd(nCrc, self.nCounter)
        for i in range(len(self.anData)):
            nCrc = CrcAdd(nCrc, self.anData[i])
        return nCrc == self.nChecksum

    def IsEqual(self, oOtherMsg):
        return self.anData == oOtherMsg.anData

def PrintSignals(aSignalValues, aSignalMap=[]):
    strLine = ''
    mqttLineB = '{'
    mqttLineStr = '{'
    #print('')
    for strName, fValue in sorted(aSignalValues.items()):
        if len(mqttLineB)>1: mqttLineB += ","
        if len(mqttLineStr)>1: mqttLineStr += ","
        if len(aSignalMap)>0:
            if aSignalMap[strName][0] == 'b':   #Bit-Signals
                mqttLineB+= ('"' + strName + '":' + '' + "{:}".format(fValue)+'')
                strSignal = ("%20s: %1.0f  " % (strName, fValue));
            elif aSignalMap[strName][3]<0.1:   #Float-Signals
                strSignal = ("%20s: %8.3f %s " % (strName, fValue, aSignalMap[strName][4]));
            else:
                mqttLineStr+= ('"' + strName + '":' + '' + "{:.1f}".format(fValue)+'')
                strSignal = ("%20s: %6.1f %s " % (strName, fValue, aSignalMap[strName][4]));
        else:
            strSignal = ("%20s: %8.3f " % (strName, fValue));
        strLine += (strSignal + ' '*(40-len(strSignal)));
    mqttLineB += '}'
    mqttLineStr += '}'
    if len(mqttLineB)>2 : print(mqttLineB)
    if len(mqttLineB)>2 : mqttc.publish("kwb/mqttLineB", mqttLineB)
    if len(mqttLineStr)>2 : print(mqttLineStr)
    if len(mqttLineStr)>2 : mqttc.publish("kwb/mqttLineStr", mqttLineStr)
    if len(mqttLineStr)>2 : sys.exit()
    #time.sleep(10)

def HandleMsg(oMsg):
    #decode message signals
    aSignals = list()
    if (not DECODE_ALL):
        aSignalMap = aaSignalMaps[oMsg.nID]
    else:
        aSignalMap = list()
    aSignals = oMsg.Decode(aSignalMap)  
    if ((oMsg.nCounter % 20 == 0) & DO_DISPLAY):
        global aoLastDispMsg
        if oMsg.nType==MSG_TYPE_CTRL:
           os.system('clear')                  
        #PrintRawMsg(oMsg, aoLastDispMsg[oMsg.nID])       
        PrintSignals(aSignals, aSignalMap)      

def ReceiveMsg(oMsg):
    nState = STATE_WAIT_FOR_HEADER
    bRxFinished = False
    while bRxFinished==False:
        nX = ord(ser.read())                    # read one byte
        if nState == STATE_WAIT_FOR_HEADER and nX == 2:
            nState = STATE_READ_MSG             # header found 
            oMsg.sTime = datetime.now()
            oMsg.nType = MSG_TYPE_CTRL          # -> CtrlMessage
        elif nState == STATE_READ_MSG:
            if nX==0:                           # header invalid -> start again
                nState = STATE_WAIT_FOR_HEADER
            elif nX==2:                         # extended header -> SenseMessage
                nState = STATE_READ_MSG
                oMsg.nType = MSG_TYPE_SENSE
            else:
                oMsg.nLen = nX                  # current byte: Message Length
                oMsg.nID = ord(ser.read())      # next byte: Message ID
                oMsg.nCounter = ord(ser.read()) # next byte: Message Counter
                nDataLen = oMsg.nLen - 4 - 1    
                oMsg.anData = bytearray(nDataLen) # Data Length = Message Length without the header and checksum
                for i in range(nDataLen):
                    nX = ord(ser.read())        # read Data bytes
                    oMsg.anData[i]=nX
                    if nX == 2:                 # "2" in data stream is followed by "0" ...
                        ser.read()              # this should be a 0 ...
                oMsg.nChecksum = ord(ser.read())               
                bRxFinished = True                     
    return oMsg.IsCrcOk()                       # CRC check

# *** Main ***    
bLogCycle = False
l = 1
aoLastDispMsg = [cMessage(count) for count in range(0,255)]
aoLastMsg = [cMessage(count) for count in range(0,255)]
oMsg = cMessage()

sTimeLastLog = datetime.today()

DO_LOGGING = 'NO_LOGGING' not in sys.argv
DO_DISPLAY = 'NO_DISPLAY' not in sys.argv 

mqttc = mqtt.Client("pyhton_kwb")
mqttc.connect("192.168.1.70", 1883)

while l==1:
    ReceiveMsg(oMsg)
    HandleMsg(oMsg)
    #aoLastMsg[oMsg.nID].CopyFrom(oMsg) # save last message 

ser.close()
#mqttc.Close()

Leider wird mir mit der Wemos Variante keine Daten per MQTT weitergegeben, also gehe ich davon aus das auch keine Daten gelesen werden konnten.

Bei mir handelt es sich um eine Easyfire Anlage mit Heizkreis 1.

Wie kann ich nun deinen Script anpassen um an meine Werte zu kommen ?

Mit besten Grüssen, und Danke für deine Arbeit.

kimi1983 commented 2 years ago

Hier ist was mein Wemos Adapter mir per MQTT ausgibt :

bytes read RS485: 10 2 21 17 120 88 140 128 16 129

Gelbwichtel commented 2 years ago

Hi, also für mich sieht das so aus, als wenn du dich an die Doku von Dirk Abel gehalten hast. Philip hat aber scheinbar einige bzw. ganz andere Werte aus seinem Protokoll ermittelt und umgesetzt. Auch meine Werte decken sich eher mit denen von Philip als mit denen von Dirk. Vielleicht ist das ja auch dein Problem. @Philip: hast du vielleicht eine Aufstellung zu deinen gefundenen Control-/ und SenseBytes gemacht, die du hier noch einfügen kannst.

kimi1983 commented 2 years ago

Ok verstehe. Aber wo kann ich dieser Werte, welche ja bei mir Daten geben bei dir einsetzen :

aaSignalMaps = [ {} for i in range(255) ]

aaSignalMaps[16] = {

Name: Type='b'(bit), Offset, Bit

#Name: Type='s'(signed)/'u'(unsigned), Offset, Length, Factor, Unit
'T_Vorlauf_HK1': ('s',5, 2, 0.1,'C'),
'T_Ruecklauf_Kessel': ('s',7, 2, 0.1,'C'),
'T_Boiler': ('s',9, 2, 0.1,'C'),
'T_Kessel':  ('s',11, 2, 0.1,'C'),
'T_Aussen': ('s',17, 2, 0.1,'C'),
'T_Rauchgas': ('s',19, 2, 0.1,'C'),
'T_Steuerung' : ('s',21, 2, 0.1,'C'),
'T_Stokerkanal' : ('s',29, 2, 0.1,'C')}

aaSignalMaps[17] = {

Name: Type='b'(bit), Offset, Bit

#Name: Type='s'(signed)/'u'(unsigned), Offset, Length, Factor, Unit  
'Aschebehaelter': ('b',3, 6),
'Pellet_Vorrat': ('b',4, 3),
'Brandschutzklappe' : ('b',1,1),
'Reinigung' : ('b',2,1),
'Alarm2': ('b',1,2),
'Alarm1': ('b',1,3),
'Boiler_Pumpe': ('b',1,5),    
'HK1_Pumpe': ('b',1,7),
'Ascheaustragung': ('b', 2,0),
'Reinigung': ('b',2,1),
'HK1_Mischer': ('b',2,6),    
'Hauptrelais': ('b',3,4),
'Raumaustragung': ('b',3,6),
'Kessel_AN': ('b',15,0)}
Gelbwichtel commented 2 years ago

Ich denke es bleibt dir nichts anders übrig, als dass du Philips Code genauer unter die Lupe nimmst. Das wesentliche ist in der Loop zu finden ... Kessel.Leistung = lfaktor * getval2(anData, 12, 2, 0.05, 0); Kessel.Pumpepuffer = getbit(anData, 2, 7); Kessel.Zuendung = getbit(anData, 16, 2); Kessel.KeineStoerung = getbit(anData, 3, 0); Kessel.Reinigung = getbit(anData, 3, 7); Kessel.Drehrost = getbit(anData, 3, 6); Kessel.Raumaustragung = getbit(anData, 9, 2); Kessel.RLAVentil = getbit(anData, 2, 3); ... Wäre dann z.B. bei dir wahrscheinlich Kessel.Reinigung = getbit(anData, 2, 1); usw.

kimi1983 commented 2 years ago

Ja stimmt da könntest du Recht haben.

Ich werde das morgen mal ausprobieren.

Danke schonmal vorab, Kim

windundsterne commented 2 years ago

Einige der Werte aus der Dokumentaion hatten für meinen Kessel gepasst, viele aber leider nicht. Daher hab ich einige ergänzt, andere auch geändert.

LG Philip

windundsterne commented 2 years ago

Hier ist was mein Wemos Adapter mir per MQTT ausgibt :

bytes read RS485: 10 2 21 17 120 88 140 128 16 129

An den Werten siehts Du das das auslesen des rs485 zu funktionieren scheint. weitere Daten kommen erst nach dem Updateintervall von 5 min. z.B. 2022-02-05_12:31:00 MQTT2_kwb info: Booting (UART0 Serial) 2022-02-05_12:31:00 MQTT2_kwb UDfaktor: 1.4900 2022-02-05_12:31:00 MQTT2_kwb kwh: 0.000 2022-02-05_12:31:00 MQTT2_kwb rec: bytes read RS485: 10 32 1 0 1 0 1 0 1 0 2022-02-05_12:31:00 MQTT2_kwb updatemin: 5 2022-02-05_12:36:00 MQTT2_kwb errors: 5442 / 51 2022-02-05_12:36:00 MQTT2_kwb Pellets: 0 2022-02-05_12:36:00 MQTT2_kwb PelletsNA: 0 2022-02-05_12:36:00 MQTT2_kwb Stoerung: 0 2022-02-05_12:36:00 MQTT2_kwb deltaPelletsh: 0 2022-02-05_12:36:00 MQTT2_kwb Kesseltemperatur: 54.3 2022-02-05_12:36:00 MQTT2_kwb Rauchgastemperatur: 78.2 2022-02-05_12:36:00 MQTT2_kwb Saugzug: 1342 / 2510 2022-02-05_12:36:00 MQTT2_kwb Unterdruck: 15.5 2022-02-05_12:36:00 MQTT2_kwb Kessel: brennt 2022-02-05_12:36:00 MQTT2_kwb Anforderung: 1 2022-02-05_12:36:00 MQTT2_kwb photodiode: 25 2022-02-05_12:37:37 MQTT2_kwb Reinigung: 1 2022-02-05_12:40:38 MQTT2_kwb Reinigung: 0 2022-02-05_12:41:00 MQTT2_kwb errors: 5484 / 23 2022-02-05_12:41:00 MQTT2_kwb Pellets: 0 2022-02-05_12:41:00 MQTT2_kwb PelletsNA: 0 2022-02-05_12:41:00 MQTT2_kwb Pumpepuffer: 1 2022-02-05_12:41:00 MQTT2_kwb Kesseltemperatur: 55.6 2022-02-05_12:41:00 MQTT2_kwb Rauchgastemperatur: 68.0 2022-02-05_12:41:00 MQTT2_kwb Saugzug: 2005 / 864 2022-02-05_12:41:00 MQTT2_kwb Unterdruck: 103.8 2022-02-05_12:41:00 MQTT2_kwb Kessel: brennt 2022-02-05_12:41:00 MQTT2_kwb Anforderung: 1 2022-02-05_12:41:00 MQTT2_kwb photodiode: 24 2022-02-05_12:44:41 MQTT2_kwb Schneckenlaufzeit: 29

Gruß Philip

rmeissn commented 1 year ago

Ich schätze der EasyFire (1) befüllt die Nachrichten etwas anders als der EasyFire 2, weswegen Werte an anderen Stellen stehen. Die aktuellen Werte im Repo kommen für meinen EasyFire 2 hin. Dass sich die Befüllung der Nachrichten zwischen den Geräteversionen ändert, wäre aus Informatik-Perspektive nachvollziehbar.