Ankündigung

Einklappen
Keine Ankündigung bisher.

Smartmeter Plugin - Tester gesucht

Einklappen
X
 
  • Filter
  • Zeit
  • Anzeigen
Alles löschen
neue Beiträge

    Smartmeter Plugin - Tester gesucht

    Ich hab für DLMS/D0/IEC-62056 Zähler ein Plugin fertig gemacht.

    Das Plugin kann in der Kommandozeile aufgerufen werden und zeigt dann die verfügbaren Werte des Zählers an:

    Code:
    python /usr/local/smarthome/plugins/dlms/__init__.py /dev/ttyUSB0 auto
    Mitzugeben sind die Parameter device und baudrate.
    Die Baudrate wird mit "auto" automatisch anhand der Zählerkennung ermittelt, kann aber auch mit "300" auf z.B. 300baud verbleiben.

    Ergebnis:
    Code:
    reading from device: /dev/ttyUSB0
    baudrate: auto
    /LGZ4ZMF100AC.M23
    1.8.1 004442.544 kWh
    1.8.2 000000.000 kWh
    1.8.3 000000.000 kWh
    1.8.4 000000.000 kWh
    1.8.0 004442.544 kWh
    2.8.0 000000.000 kWh
    15.8.0 004442.544 kWh
    32.7 240 V
    52.7 240 V
    72.7 241 V
    31.7 003.36 A
    51.7 001.11 A
    71.7 001.85 A
    16.7 000.78 kW
    Reading takes: 4.73436403275
    Hier sehen wir jetzt die Obis-Kennzahlen und deren Werte. Die braucht man für das anlegen der items.

    items.conf
    Code:
    [zaehler]
        [[stand]]
            type = num
            dlms_code = "1.8.1"
        [[strom_l1]]
            type = num
            dlms_code = "31.7"
        [[strom_l3]]
            type = num
            dlms_code = "71.7"
    Das Plugin liegt unter /plugins/dlms/__init__.py
    plugin.conf:
    Code:
    [smartmeter]
        class_name = DLMS
        class_path = plugins.dlms
        serialport = "/dev/ttyUSB0"
        baudrate = auto
        cycle = 60
    Hier also alle 60 Sekunden die Werte für Zählerstand absolut und Stromstärke L1 & L3.

    ToDo:
    - Unterstützung von mehreren Zählern/seriellen Ports im Plugin
    - Unterstützung von mehreren Zählern über einen IR-Auslesekopf

    Momentan fehlt mir noch die Idee wie ich bei mehreren Zählern/Ausleseköpfen die items der passenden Zähler-id zuordne und dass noch mit dem sheduler kombiniere.

    Grüße

    ../plugins/dlms/__init__.py:
    PHP-Code:
    #!/usr/local/bin/python

    import os
    import sys
    import logging
    import struct
    import time

    logger 
    logging.getLogger('')

    class 
    DLMS():
        
    def __init__(selfsmarthomeserialportbaudratecycle):
            
    self._sh smarthome
            self
    .port serialport
            self
    .obis = {}
            
    self.baud baudrate
            self
    .cycle int(cycle)
            
        
    def run(self):
            
    self.alive True 
            self
    ._sh.scheduler.add('DLMS'self.connectcycle=self.cycle)
            
    logger.debug("Cycle: {0}".format(self.cycle))
            global 
    from_shell
            from_shell 
    False
            self
    .connect()
            
        
    def stop(self): 
            
    self.alive False 
        
        def connect
    (self):
            
    logger.debug("DLMS: connect")
            
    request "\x2f\x3f\x21\x0d\x0a"
            
    cmd "echo '"+request+"' | socat -T 1 - "+self.port+",raw,echo=0,b300,parenb=1,parodd=0,cs7,cstopb=0"
            
    try:
                
    self.id os.popen(cmd).readline()
            
    except:
                if 
    from_shell:
                    print(
    "[connect] error main: {0}".format(e))
                else:
                    
    logger.warning("DLMS: {0}".format(e))
                
            if 
    len(self.id) > 0:
                if 
    from_shell:
                    print 
    self.id.strip()
                    
    self.ack(self.id)
                else:
                    
    self.ack(self.id)
            
        
    def ack(self,id):    
            
    speedrates = ['300','600','1200','2400','4800','9600']
            
    acks = ["\x06\x30\x30\x30\x0d\x0a","\x06\x30\x31\x30\x0d\x0a","\x06\x30\x32\x30\x0d\x0a","\x06\x30\x33\x30\x0d\x0a","\x06\x30\x34\x30\x0d\x0a","\x06\x30\x35\x30\x0d\x0a"]
            
    arr = [[0,"300","\x06\x30\x30\x30\x0d\x0a"],[1,"600","\x06\x30\x31\x30\x0d\x0a"],[2,"1200","\x06\x30\x32\x30\x0d\x0a"],[3,"2400","\x06\x30\x33\x30\x0d\x0a"],[4,"4800","\x06\x30\x34\x30\x0d\x0a"],[5,"9600","\x06\x30\x35\x30\x0d\x0a"]]
            
            if 
    self.baud == "auto":
                
    speed speedrates[int(self.id[4])]
                
    ack acks[int(self.id[4])]
            else:
                for 
    n in arr:
                    if 
    n[1] == self.baud:
                        
    speed n[1]
                        
    ack n[2]
            try:
                
    cmd "echo '"+ack+"' | socat -T 1 - "+self.port+",raw,echo=0,b300,parenb=1,parodd=0,cs7,cstopb=0; socat -T 1 - "+self.port+",raw,echo=0,b"+speed+",parenb=1,parodd=0,cs7,cstopb=0"
                
    lines os.popen(cmd).readlines()
            
    except:
                print(
    "[ack] error main: {0}".format(e))
            
    self.parse(lines)

        
    def parse(self,lines):
            for 
    line in lines[:-2]:
                if 
    "*" in line:
                    
    line[:-2].split("(")
                    
    i[1].split("*")
                    
    j[1].split(")")
                    
    c_obis i[0]
                    
    c_value float(j[0])
                    
    c_unit k[0]
                    if 
    not from_shell:
                        
    self.refresh(c_obis,c_value,c_unit)
                    else:
                        print 
    i[0] + " " j[0] + " " k[0]
        
        
    def refresh(self,c_obis,c_value,c_unit):
            for 
    codes in self.obis
                if 
    codes == c_obis
                    
    self.obis[codes](c_value,'DLMS''refresh')
                    
    #logger.debug("DLMS: Parameter/Refresh: {0}".format(codes)) 

        
    def parse_item(selfitem):
            if 
    'dlms_code' in item.conf:
                
    logger.debug("parse item: {0}".format(item))
                
    obis_code item.conf['dlms_code']
                
    self.obis[obis_code] = item
                logger
    .debug("DLMS: new set = item:{0} obis:{1}".format(item,obis_code))
                
    def main():
        try:
            
    start time.time()
            
    #serialport = '/dev/usbserial-alcdut1'
            #serialport = '/dev/usbserial-A600eZF1'
            
    if sys.argv[1]:
                
    serialport sys.argv[1]
                print (
    "reading from device: {0}".format(serialport))
            if 
    sys.argv[2]:
                
    baudrate sys.argv[2]
                print (
    "baudrate: {0}".format(baudrate))
            
    smarthome ''
            
    cycle 60
            
    global from_shell
            from_shell 
    True
            meter 
    DLMS(smarthome,serialport,baudrate,cycle)
            
    meter.connect()

            
    cycletime time.time() - start
            
    print ("Reading takes: {0}".format(cycletime))
            
        
    except Exceptione:
            print 
    "[main]: {0}".format(e)
            print 
    "usage: __init__.py <serial_port> <baudrate>"
            
    print "baudrate: auto,300,600,1200,2400,4800,9600"
            
    return 1

    if __name__ == "__main__":
        
    sys.exit(main()) 
    Umgezogen? Ja! ... Fertig? Nein!
    Baustelle 2.0 !

    #2
    Hallo Mirko,

    +1

    Zitat von JuMi2006 Beitrag anzeigen
    Momentan fehlt mir noch die Idee wie ich bei mehreren Zählern/Ausleseköpfen die items der passenden Zähler-id zuordne und dass noch mit dem sheduler kombiniere.
    vllt. gefällt Dir die Lösung aus dem mpd-Plugin: https://github.com/mknx/smarthome/bl..._init__.py#L79

    Doku folgt noch...

    Bis bald

    Marcus

    Kommentar


      #3
      Hallo Marcus,

      Danke nochmal für Deine Tips, haben mir gut geholfen das ganze zu verstehen.
      Die Doku für das Plugin mache ich noch fertig, ansonsten nervt mich hier gerade wieder das ganze serielle/socat Gefrickel mit den unzuverlässigen eHZ.

      Ich persönlich bleib da bei meinen meinen zwei perl/cronjob-scripten die seit über einem Jahr fehlerfrei laufen. Vielleicht will sich jemand anders ja dessen noch mal annehmen - ich kann/will da im Moment keine weitere Zeit versenken.

      Grüße
      Umgezogen? Ja! ... Fertig? Nein!
      Baustelle 2.0 !

      Kommentar


        #4
        Hallo Mirko,

        wenn Du mal wieder Zeit und Lust hast, kannst Du Dir ja mal das DMX-Plugin ansehen, dort wird auch über eine serielle Schnittstelle kommuniziert.
        Das ist ziemlich direkt und stabil.
        Ich verstehe auch nicht wieso das WG-Lager socat so verehrt.

        Bis bald

        Marcus

        Kommentar


          #5
          Die serielle mag einfach keine Baudratenumstellung innerhalb der Kommunikation, das ist mir weder mit Python noch mit Perl gelungen ... tausend mal die man-pages gelesen und verstanden ... erst deswegen bin ich wieder beim socat gelandet. In der Kommunikation gibt es da ja keine riesigen Unterschiede zwischen perl und Python was die serielle angeht -> open/recv/close.

          Das Plugin stand hier such schon komplett auf serieller Ebene, aber ich hab einfach keine Lust 120! Sekunden auf einen Datensatz meines WP_Zählers zu warten weil der nur mit 300baud dröselt und aus 120 Zeilen genau eine gebraucht wird.

          Der socat ist schon nett für die serielle im LAN, ansonsten macht er das Leben auch nicht einfacher, das bisschen "\n" und ”\r” was man meist sucht findet man auch leichter/verständlicher.

          Grüße
          Umgezogen? Ja! ... Fertig? Nein!
          Baustelle 2.0 !

          Kommentar


            #6
            Hi!

            Ich hab heute mal Mirkos Code genommen (leider hatte ich nur eine Vorversion ohne vollständige Plugin-Funktionalität gefunden - ist dieses Plugin nicht im develop?) und für meine Zwecke angepasst. Da mein Zähler nur 300 Baud kann ist die Baudraten-Umschaltung entfallen (konnte ich nicht testen - mein Zähler würde aber korrekt als 300 baud erkannt). Ansonsten Umsetzung auf Python 3.2/3.3 und python3-serial (ohne socat). Das Splitting/Matching habe ich per RegEx etwas strenger gemacht.

            Evtl. kann man ein Best-of machen und das einchecken? Mirko?

            Mehrere Zähler: Wäre nett, aber evtl. sollte das über einen Modifikation der Plugin-Engine gelöst werden, so dass man einfach das Plugin in der Config zweimal instanziiert? Dann im Plugin und jedem Item nur je ein Attribut "dlms_meter", was matchen muss?

            plugin/dlms/__init__.py
            PHP-Code:
            #!/usr/local/bin/python

            import logging
            import time
            import serial
            import re

            logger 
            logging.getLogger('DLMS')

            class 
            DLMS():
                
            def __init__(selfsmarthomeserialportbaudrate "300"update_cycle "60"):
                    
            self._sh smarthome
                    self
            ._update_cycle int(update_cycle)
                    
            self._obis_codes = {}
                    
            self._serial serial.Serial(serialportint(baudrate), bytesize serial.SEVENBITSparity serial.PARITY_EVENtimeout 2)

                
            def run(self):
                    
            self.alive True 
                    self
            ._sh.scheduler.add('DLMS'self._update_valuesprio 5cycle self._update_cycle)

                
            def stop(self): 
                    
            self.alive False 
                    self
            ._sh.scheduler.remove('DLMS')

                
            def _update_values(self):
                    
            logger.debug("dlms: update")
                    
            request "/?!\r\n"
                    
            start time.time()
                    
            self._serial.write(bytes(request'utf-8'))
                    
            response bytes()
                    try:
                        while 
            self.alive:
                            
            prev_len len(response)
                            
            response += self._serial.read()
                            if (
            prev_len == len(response)):
                                break
                    
            except Exception as e:
                        
            logger.warning("dlms: {0}".format(e))
                    
            logger.debug("dlms: Reading took: {:.2f}s".format(time.time() - start))
                    for 
            line in re.split('\r\n'str(response,'ascii')):
            #            if re.match('[0-9]+\.[0-9]\.[0-9](.+)', line): # für x.y.z(foo)
                        
            if re.match('[0-9]+\.[0-9].+(.+)'line): # für x.y(foo)
                            
            try:
                                
            data re.split(r'[(*)]+'line)
                                
            logger.debug("dlms: {} = {} {}".format(data[0], data[1], data[2]))
                                if 
            data[0in self._obis_codes:
                                    for 
            item in self._obis_codes[data[0]]['items']:
                                        
            item(data[1], 'DLMS''OBIS {}'.format(data[0]))
                            
            except Exception as e:
                                
            logger.warning("dlms: line={} exception={}".format(linee))

                
            def parse_item(selfitem):
                    if 
            'dlms_obis_code' in item.conf:
                        
            logger.debug("parse item: {0}".format(item))
                        
            obis_code item.conf['dlms_obis_code']
                        if 
            not obis_code in self._obis_codes:
                            
            self._obis_codes[obis_code] = {'items': [item], 'logics': []}
                        else:
                            
            self._obis_codes[obis_code]['items'].append(item)
                    return 
            None

            if __name__ == '__main__':
                
            logging.basicConfig(level=logging.DEBUG)
                
            myplugin Plugin('DLMS')
                
            myplugin.run() 
            etc/plugin.conf
            Code:
            [dlms]
                class_name = DLMS
                class_path = plugins.dlms
                serialport = /dev/ttyO1
            items/demo.conf
            PHP-Code:
            [Stromzaehler]
              [[
            Bezug]]
                [[[
            Energie]]]
                  
            type num
                  dlms_obis_code 
            1.8.1
                
            [[[Leistung]]]
                  
            type num
                  
            eval = "(3600000 / sh.Stromzaehler.Bezug.Energie.prev_change()) if not isinstance(sh.Stromzaehler.Bezug.Energie.prev_change(), datetime.datetime) else 0"
                  
            eval_trigger Stromzaehler.Bezug.Energie
              
            [[Lieferung]]
                [[[
            Energie]]]
                  
            type num
                  dlms_obis_code 
            2.8.1
                
            [[[Leistung]]]
                  
            type num
                  
            eval = "(3600000 / sh.Stromzaehler.Lieferung.Energie.prev_change()) if not isinstance(sh.Stromzaehler.Lieferung.Energie.prev_change(), datetime.datetime) else 0"
                  
            eval_trigger Stromzaehler.Lieferung.Energie 
            Die Leistungen muss ich mir leider berechnen, da mein "Sparzähler" tatsächlich nur die Zählerstände rausrückt... Die Rechnungen sind richtig, allerdings ist der erste berechnete Wert immer falsch, da die falsche Zeit zum Ansatz kommt (weil der erste "_change" durch den Start von sh.py kommt und nicht durch einen echten Wechsel). Dass kann man nur verhindern, indem man die Zeit von zwei echten Zählerstandswechseln abwartet.

            Grüße
            Robert

            Kommentar


              #7
              Kurz ein paar Stichpunkte:

              Plugin:
              Die gepostete Version sollte eigentlich als Plugin auch lauffähig sein. Ich muss die Tage mal in den untiefen der Cloud suchen ob ich noch was aktuelleres habe. Ich glaube der Code ist nicht in develop - nur hier.

              Mehrere Zähler:
              Da gibt es 2 Varianten. Einmal gibt es Zähler di die Werte von anderen angeschlossenen Zählern mit übermitteln, zum zweiten dann eben die Differenzierung zwischen zwei "normalen" Zählern. Für zweitens würde ja eine Zähler-ID in der intem.conf reichen.

              Baudratenumstellung:
              Die brauchts imho. Wie gesagt, ich habs weder in Perl noch Python hinbekommen. Die man pages glaube ich dabei wirklich verstanden zu haben und Beispielcodes im Netz deckten sich mit meinem Vorgehen.

              Generell:
              Testen kann ich das frühestens Mitte der Woche. Die Zähler sind vom Timing her aber auch höchst unterschiedlich (Zeit zwischen Initialisierung und Befehl Baudrate) und machen es damit nicht einfacher. Manch Zähler will auch unbedingt ein ACK haben (000 bis 040) und verweigert sonst auch ohne Baudratenumstellung die Ausgabe, das fehlt hier auf jeden Fall.
              Umgezogen? Ja! ... Fertig? Nein!
              Baustelle 2.0 !

              Kommentar


                #8
                Zitat von JuMi2006 Beitrag anzeigen
                Die gepostete Version sollte eigentlich als Plugin auch lauffähig sein. Ich muss die Tage mal in den untiefen der Cloud suchen ob ich noch was aktuelleres habe. Ich glaube der Code ist nicht in develop - nur hier.
                Stimmt. Als ich den THread hier vor dem Posten wiederenddeckt habe habe ich mir nen Wolf gesucht, wo ich die andere Version her habe. Irgendwo im volkszähaehler.org/ip-Symcom/sonstwo hattest du das nämlich auch gepostet.

                Zitat von JuMi2006 Beitrag anzeigen
                Da gibt es 2 Varianten. Einmal gibt es Zähler di die Werte von anderen angeschlossenen Zählern mit übermitteln, zum zweiten dann eben die Differenzierung zwischen zwei "normalen" Zählern. Für zweitens würde ja eine Zähler-ID in der intem.conf reichen.
                Die erste Variante kenne ich gar nicht. Bei der zweiten: Ja stimmt, wenn man die korrekte Zählerkennung wie mit DLMS ausgelesen weiß, wäre das natürlich die logischte ID für die Items.

                Zitat von JuMi2006 Beitrag anzeigen
                Baudratenumstellung:
                Die brauchts imho. Wie gesagt, ich habs weder in Perl noch Python hinbekommen. Die man pages glaube ich dabei wirklich verstanden zu haben und Beispielcodes im Netz deckten sich mit meinem Vorgehen.
                Zumindest die Holzhammermathode mit Close, del, neuer Klasse sollte ja funktionieren!?

                Zitat von JuMi2006 Beitrag anzeigen
                Testen kann ich das frühestens Mitte der Woche. Die Zähler sind vom Timing her aber auch höchst unterschiedlich (Zeit zwischen Initialisierung und Befehl Baudrate) und machen es damit nicht einfacher. Manch Zähler will auch unbedingt ein ACK haben (000 bis 040) und verweigert sonst auch ohne Baudratenumstellung die Ausgabe, das fehlt hier auf jeden Fall.
                Passt schon. Die Zähler sind da wohl in der Tat recht eigen. Schön wäre es halt, wenn man eine für zumindest bei unsere Zähler lauffähige Version ins GIT packen könnte, um diese auch standardmäßig anderen Benutzern anzubieten (ohne hier im Forum zu suchen & c&p).

                Grüße
                Robert

                Kommentar


                  #9
                  Den Holzhammer hatte ich auch schon ... ich weiß aber nicht mehr ob in Perl und/oder Python. Aber vielleicht hat sich ja bei Python3 was getan oder ich war wirklich zu dämlich ... das will ich nicht ausschließen.

                  Ja man könnten nen eigenen branch dafür machen. Selbst bei meinen 2 Zählern mag einer und der andere nicht mit dem was hier gepostet ist. Ich hab beruflich nur ne anstrengende Woche vor mir und Familie gibts ja auch noch.

                  Sollten wir aber gemeinsam verfolgen denn was bei einem geht muss längst nicht auch beim anderen laufen. Daher wäre es schon gut wenn da mehrere Leute mittesten.

                  @greentux: Vielleicht kann Dein Bekannter seinen Code schonmal posten und wir testen den?

                  Grüße
                  Umgezogen? Ja! ... Fertig? Nein!
                  Baustelle 2.0 !

                  Kommentar


                    #10
                    Abei eine Version mit automatischer Umschaltung (drei verschiedene Methoden inkl. Holzhammer)

                    Funktioniert sogar mit meinem Zähler (Landis&Gyr ZMD120) mit 4800 Baud perfekt - danke für das Bestehen auf Umschaltung! Ich hatte schon aufgegeben meinen Zähler umzuschalten! :-D

                    Übrigens läuft mein Zähler dann mit 4800 Baud weiter - also es muss nich jedesmal zurückgeschaltet werden!? Evtl. funktioniert das auch mit deinem?

                    [bitte Plugin-Code von nächster Seite nehmen]

                    Kommentar


                      #11
                      Hi,

                      habe das Auslesen meines Stromzählers über ein ein fast baugleiches Plugin umgesetzt. Das Plugin werkelt schon seit 3 Monaten stabil herum, hat es aber aus Zeitgründen noch nicht in "öffentliche" Codequalität geschafft. Ich lese die Werte so aus:

                      def update_status(self):

                      # open serial connection
                      self.ser.open()
                      self.ser.flushInput()

                      # wait for start of next datablock
                      while True:
                      line = self.ser.readline()
                      if line.find('!') >= 0:
                      break

                      # read next datablock
                      datablock=[]

                      while True:
                      line = self.ser.readline()
                      datablock.append(line)
                      if line.find('!') >= 0:
                      break

                      #close serial connection
                      self.ser.close()

                      Der Rest ist fast identisch. Hoffe am Wochenende kann ich den zweiten in Betrieb nehmen, die Unterstützung für mehrere fehlt bei meiner Variante noch. Sollte aber nicht schwer sein. Dann kann ich die Variante auch mal mit testen.

                      Gruß
                      Mark

                      Kommentar


                        #12
                        Meine Kernfrage: Baudratenumstellung?
                        Umgezogen? Ja! ... Fertig? Nein!
                        Baustelle 2.0 !

                        Kommentar


                          #13
                          Zitat von JuMi2006 Beitrag anzeigen
                          Meine Kernfrage: Baudratenumstellung?
                          Hast du meine letzte Version mal angeschaut (hatte die nachträglich editiert um nicht den Thread vollzuspammen)? Die funktioniert mit Baudratenumstellung wirklich klasse bei mir. Lesevorgang 2,7s, mit Möglichkeit dass auf 2,1s zu drücken.

                          Ich hack da gleich noch eben die STX/ETX-Erkennung und Checksumme rein, dann kann es auch Übertragungsfehler erkennen und müsste noch mal 0,3s schneller sein.

                          Grüße
                          Robert

                          Kommentar


                            #14
                            Smartmeter Plugin - Tester gesucht

                            Da kann ich leider nix beitragen, außer mit meinen Zählern zu testen.

                            Kommentar


                              #15
                              Zitat von Robert Beitrag anzeigen
                              Hast du meine letzte Version mal angeschaut (hatte die nachträglich editiert um nicht den Thread vollzuspammen)? Die funktioniert mit Baudratenumstellung wirklich klasse bei mir. Lesevorgang 2,7s, mit Möglichkeit dass auf 2,1s zu drücken.
                              Ja erst nach meinem Post entdeckt. Läuft aber hier nicht. Kann das aber heute nicht mehr debuggen. Ab und zu kamen mal Werte rein.
                              Umgezogen? Ja! ... Fertig? Nein!
                              Baustelle 2.0 !

                              Kommentar

                              Lädt...
                              X