Ankündigung

Einklappen
Keine Ankündigung bisher.

Smartmeter Plugin - Tester gesucht

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

  • fanta2k
    antwortet
    Zitat von Robert Beitrag anzeigen
    Nur damit es nicht durcheinander geht: "Gehen" tut das dlms-Plugin was bei sh.py nun dabei ist oder eine Version hier aus dem Thread?

    Falls die sh.py-Version: Mit Checksumme?

    Für mein Verständnis: Wofür brauchtest du die "float(data[0])"? Eigentlich hängen da doch per se "Num-Items" dran und das wird dann korrekt gewandelt? Ansonsten bau ich den Cast noch ein...
    bei mir läuft dieses hier aktuell:

    Code:
    #!/usr/bin/env python3
    # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
    #########################################################################
    # Copyright 2011 KNX-User-Forum e.V. https://knx-user-forum.de/
    #########################################################################
    # DLMS plugin for SmartHome.py. http://mknx.github.io/smarthome/
    #
    # This plugin is free software: you can redistribute it and/or modify
    # it under the terms of the GNU General Public License as published by
    # the Free Software Foundation, either version 3 of the License, or
    # (at your option) any later version.
    #
    # This plugin is distributed in the hope that it will be useful,
    # but WITHOUT ANY WARRANTY; without even the implied warranty of
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
    # GNU General Public License for more details.
    #
    # You should have received a copy of the GNU General Public License
    # along with this plugin. If not, see <http://www.gnu.org/licenses/>.
    #########################################################################
    
    import logging
    import time
    import serial
    import re
    
    logger = logging.getLogger('DLMS')
    
    
    class DLMS():
    
        def __init__(self, smarthome, serialport, baudrate="auto", update_cycle="60"):
            self._sh = smarthome
            self._update_cycle = int(update_cycle)
            if (baudrate.lower() == 'auto'):
                self._baudrate = -1
            else:
                self._baudrate = int(baudrate)
            self._obis_codes = {}
            self._serial = serial.Serial(
                serialport, 300, bytesize=serial.SEVENBITS, parity=serial.PARITY_EVEN, timeout=2)
            self._request = bytearray('\n\x06000\r\n', 'ascii')
    
        def run(self):
            self.alive = True
            self._sh.scheduler.add('DLMS', self._update_values,
                                   prio=5, cycle=self._update_cycle)
    
        def stop(self):
            self.alive = False
            self._serial.close()
            self._sh.scheduler.remove('DLMS')
    
        def _update_values(self):
            logger.debug("dlms: update")
            start = time.time()
            init_seq = bytes('\n/?!\r\n', 'ascii')
            self._serial.flushInput()
            self._serial.write(init_seq)
            response = bytes()
            prev_length = 0
            try:
                while self.alive:
                    response += self._serial.read()
                    length = len(response)
                    # break if timeout or newline-character
                    if (length == prev_length) or ((length > len(init_seq)) and (response[-1] == 0x0a)):
                        break
                    prev_length = length
            except Exception as e:
                logger.warning("dlms: {0}".format(e))
            # remove echoed chars if present
            if (init_seq == response[:len(init_seq)]):
                response = response[len(init_seq):]
            if (len(response) >= 5) and ((response[4] - 0x30) in range(6)):
                if (self._baudrate == -1):
                    baud_capable = 300 * (1 << (response[4] - 0x30))
                else:
                    baud_capable = self._baudrate
                if baud_capable > self._serial.baudrate:
                    try:
                        logger.debug(
                            "dlms: meter returned capability for higher baudrate {}".format(baud_capable))
                        # change request to set higher baudrate
                        self._request[2] = response[4]
                        self._serial.write(self._request)
                        logger.debug("dlms: trying to switch baudrate")
                        switch_start = time.time()
                        # Alt1:
                        #self._serial.baudrate = baud_capable
                        # Alt2:
                        #settings = self._serial.getSettingsDict()
                        #settings['baudrate'] = baud_capable
                        # self._serial.applySettingsDict(settings)
                        # Alt3:
                        port = self._serial.port
                        self._serial.close()
                        del self._serial
                        logger.debug("dlms: socket closed - creating new one")
                        self._serial = serial.Serial(
                            port, baud_capable, bytesize=serial.SEVENBITS, parity=serial.PARITY_EVEN, timeout=2)
                        logger.debug(
                            "dlms: Switching took: {:.2f}s".format(time.time() - switch_start))
                        logger.debug("dlms: switch done")
                    except Exception as e:
                        logger.warning("dlms: {0}".format(e))
                        return
                else:
                    self._serial.write(self._request)
            response = bytes()
            prev_length = 0
            try:
                while self.alive:
                    response += self._serial.read()
                    length = len(response)
                    # break if timeout or "ETX"
                    if (length == prev_length) or ((length >= 2) and (response[-2] == 0x03)):
                        break
                    prev_length = length
            except Exception as e:
                logger.warning("dlms: {0}".format(e))
            logger.debug("dlms: Reading took: {:.2f}s".format(time.time() - start))
            # remove echoed chars if present
            if (self._request == response[:len(self._request)]):
                response = response[len(self._request):]
            # perform checks (start with STX, end with ETX, checksum match)
            checksum = 0
           # for i in response[1:]:
           #     checksum ^= i
           # if (len(response) < 5) or (response[0] != 0x02) or (response[-2] != 0x03) or (checksum != 0x00):
           #     logger.warning(
           #         "dlms: checksum/protocol error: response={} checksum={}".format(' '.join(hex(i) for i in response), checksum))
           #     return
            #print(str(response[1:-4], 'ascii'))
            for line in re.split('\r\n', str(response[1:-4], 'ascii')):
                # if re.match('[0-9]+\.[0-9]\.[0-9](.+)', line): # allows only
                # x.y.z(foo)
                if re.match('[0-9]+\.[0-9].+(.+)', line): # allows also x.y(foo)
                    try:
                        #data = re.split('[(*)]', line)
                        data = line.split('(')
                        data[1:3] = data[1].strip(')').split('*')
                        if (len(data) == 2):
                            logger.debug("dlms: {} = {}".format(data[0], data[1]))
                        else:
                            logger.debug(
                                "dlms: {} = {} {}".format(data[0], data[1], data[2]))
                        if data[0] in self._obis_codes:
                            for item in self._obis_codes[data[0]]['items']:
                                item(float(data[1]), 'DLMS', 'OBIS {}'.format(data[0]))
                    except Exception as e:
                        logger.warning(
                            "dlms: line={} exception={}".format(line, e))
    
        def parse_item(self, item):
            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

    checksum und umschalten auskommentiert + typecast + \n vor dem string.
    das typecast nach float brauch ich wenn ich mit eval den kWk wert / 1000 dividiere.

    welcher EMH ITZ ... hier hängt auch einer ?
    gibs da unterschiedliche modele von dem ITZ ? Model 2013, Drehstrom

    Einen Kommentar schreiben:


  • Robert
    antwortet
    Hi Mirko,

    versuch mir doch bitte erst mal zu erklären, wo du die Unterschiede erkannt hast. Aus der Schleife werde ich nicht schlau.

    Bis nach Init/Umschalten/"der Schleife" sind wir uns schon mal einig, oder?

    Jetzt sendest du aber den Request scheinbar jedesmal mit 300 Baud? und schaltest dann um auf 4800?

    Bei mir läuft nach dem einmaligen Umschalten alles auf 4800 - das geht bei dir nicht?

    Wenn du das umbauen willst - ich habe hier ne fertige "zerlegte" Version, wo das Init mit Baudratenerkennung <-> Update/"Schleife" getrennt sind. Da müsste dann nur noch die Baudrate mit einem Einzeiler gewechselt werden...

    Grüße
    Robert

    Einen Kommentar schreiben:


  • JuMi2006
    antwortet
    O.K. dann versuche ich mal die Schleife ins Plugin zu portieren.

    Einen Kommentar schreiben:


  • JuMi2006
    antwortet
    @fanta2k:
    Welcher EMH ITZ ... hier hängt auch einer ?

    Einen Kommentar schreiben:


  • Robert
    antwortet
    Zitat von fanta2k Beitrag anzeigen
    bei mir läufts mit der fixen einstellung auf 300 nun stabil.
    Nur damit es nicht durcheinander geht: "Gehen" tut das dlms-Plugin was bei sh.py nun dabei ist oder eine Version hier aus dem Thread?

    Falls die sh.py-Version: Mit Checksumme?

    Für mein Verständnis: Wofür brauchtest du die "float(data[0])"? Eigentlich hängen da doch per se "Num-Items" dran und das wird dann korrekt gewandelt? Ansonsten bau ich den Cast noch ein...

    Einen Kommentar schreiben:


  • JuMi2006
    antwortet
    Zitat von Robert Beitrag anzeigen
    Ich sehe bis auf die "sleep" keinen gravierenden Unterschied!? Diverse Automatismen wie Baudrate "errechnen"/Ack basteln, Abbruchbedingungen der Leseschleifen und Umschaltung der Baudrate sind ja gleich.
    Ja natürlich, muss man ja nicht neu erfinden. Der generelle Unterschied ist aber der Loop bis zum STX vom Zähler.

    Zitat von Robert Beitrag anzeigen
    Was mir fehlt damit es bei mir überhaupt funktionieren kann ist die Unterdrückung des Echos (siehe Plugin). Checksumme wäre natürlich, sofern vom eHZ unterstützt, auch sinnvoll. Ein Aufbrechen der Init/Ack/Init/Ack/Init/Ack in Init/Ack/Ack/Ack-Sequenz funktioniert bei meinem eHZ leider auch nicht, falls du das noch vor hattest.
    Das Aufbrechen der Schleife funktioniert hier bei beiden Zählern wunderbar. Was gibt Dein Zähler denn alles als echo? Habe ich noch nie gesehen.

    Zitat von Robert Beitrag anzeigen
    Daher: Bevor wir im develop hin- und herschieben wäre es sinnvoll, wenn du - ob auch Basis des existierenden (damit z.B. parse_item bleiben kann) Plugins oder was "Neuem" - hier einen Vorschlag machst den ich und andere dann testen können. Wenn das dann bei mehreren funzt ersetzen wir das aktuelle Plugin.
    Das würde ich erst machen wenn ein funktionierendes Script zum portieren steht. Daher ja mein Post. Ausprobieren ... geht/geht nicht. Debuggen kann es dann eh nur jemand der nen Zähler hat der nicht funktioniert. Aus der Ferne macht sowas keinen Sinn.

    Zitat von Robert Beitrag anzeigen
    Beim Blick auf den Code denke ich aber, dass es eigentlich mit ein paar "sleep" getan ist?
    Leider nicht, da Deine Bedingungen doch schon ziemlich speziell sind und sich nicht mit meinen Loops vertragen. Daher denke ich sollte es eine vernünftige Basis geben. Diese gleich als Plugin zu entwickeln macht das testen nur unheimlich nervig und damit die Entwicklung langsamer.

    Einen Kommentar schreiben:


  • Robert
    antwortet
    Zitat von fanta2k Beitrag anzeigen
    bei meinem EMH ITZ kommt damit der fehler:
    Ich WETTE! darauf, dass auch du die Echo-Unterdrückung brauchst. Wenn alles nachgepflegt ist sind wir wieder beim existierenden Plugin... (plus hoffentlich den benötigten "sleep").

    Blick in Mirkos Code sagt: Antwort ist über 5, aber eben das Echo - daher geht nächste Abfrage nicht und die Variable wird nicht erzeugt.

    Einen Kommentar schreiben:


  • fanta2k
    antwortet
    bei meinem EMH ITZ kommt damit der fehler:


    admin@smarthome:/usr/smarthome$ python3 test.py
    send init:
    > /?!
    got id:
    < /?!
    Start Loop:
    send ACK:
    > 000
    switch to 300 baud
    send hex:bytearray(b'\x06000\r\n')
    ERROR: name 'new_ascii_baudrate' is not defined
    admin@smarthome:/usr/smarthome$

    Einen Kommentar schreiben:


  • Robert
    antwortet
    Zitat von JuMi2006 Beitrag anzeigen
    @Robert: Wie weiter? Wer machts? Nicht dass wir hier doppelt Zeit verschenken - ich würde es auch zum Finale bringen.
    Ich sehe bis auf die "sleep" keinen gravierenden Unterschied!? Diverse Automatismen wie Baudrate "errechnen"/Ack basteln, Abbruchbedingungen der Leseschleifen und Umschaltung der Baudrate sind ja gleich.

    Was mir fehlt damit es bei mir überhaupt funktionieren kann ist die Unterdrückung des Echos (siehe Plugin). Checksumme wäre natürlich, sofern vom eHZ unterstützt, auch sinnvoll. Ein Aufbrechen der Init/Ack/Init/Ack/Init/Ack in Init/Ack/Ack/Ack-Sequenz funktioniert bei meinem eHZ leider auch nicht, falls du das noch vor hattest.

    Daher: Bevor wir im develop hin- und herschieben wäre es sinnvoll, wenn du - ob auch Basis des existierenden (damit z.B. parse_item bleiben kann) Plugins oder was "Neuem" - hier einen Vorschlag machst den ich und andere dann testen können. Wenn das dann bei mehreren funzt ersetzen wir das aktuelle Plugin.

    Beim Blick auf den Code denke ich aber, dass es eigentlich mit ein paar "sleep" getan ist? "inter_char_timeout" können wir ja wenn nötig auch reinbauen. Kannst du nicht einfach mal die sleeps einbauen und testen? Wo hakt es denn da noch? Die Checksummen-Prüfung habe ich bereits optional gemacht - siehe aktueller develop, "use_checksum = False" dem Plugin übergeben.

    Grüße
    Robert

    P.S.: Statt der Strings 'false' und 'true' nimm doch generell eher generisch Bool True / False...

    Einen Kommentar schreiben:


  • JuMi2006
    antwortet
    Ich hab hier mal ein proof of concept:

    Es müsste lediglich der Port angepasst werden. Das ist ein reines Python Script, also kein Plugin Code, ausführen mit "python3 scriptname.py":

    Code:
    #!/usr/bin/env python3
    
    import time
    import serial
    import re
    import os
    
    serialport = '/dev/ehz-hz'
    init_seq = bytes('/?!\r\n', 'ascii')
    request = bytearray('\x06000\r\n', 'ascii')
    
    serial = serial.Serial(serialport, 300, bytesize=serial.SEVENBITS, parity=serial.PARITY_EVEN, timeout=5, interCharTimeout=2)
    serial.flushInput()
    serial.write(init_seq)
    print("send init:\n> {}".format((init_seq.decode('iso-8859-9')))[:-2])
    response = bytes()
    newlines = 0
    try:
        while newlines < 1:
            response += serial.read()
            length = len(response)
            #count newlines
            if (0x0a or 0x0d) in response:
                newlines += 1
                ehz_id = response
                print ("got id:\n< {}".format((ehz_id.decode('iso-8859-9'))[:-2]))
    except Exception as e:
        print ("No ID")
    
    if (len(response) >= 5):
        if (ehz_id[4] - 0x30) in range(6):
            new_ascii_baudrate = 300 * (1 << (response[4] - 0x30))
            print ("baudrate ascii :{}".format(new_ascii_baudrate))
            request[2] = ehz_id[4]
        print ("Start Loop:")
          
        try:
            wait = 1.0
            time.sleep(wait)
            print("send ACK:\n> {}".format((request.decode('iso-8859-9'))[:-2]))
            new = 'false'
            while new == 'false':
                response2 = bytes()
                time.sleep(0.5)
                serial.baudrate = 300
                print ("switch to 300 baud")
                serial.write(request)
                time.sleep(0.5)
                print ("send hex:{}".format(request))
                #print (os.popen('stty -F /dev/ehz-hz').read())
                serial.baudrate = new_ascii_baudrate
                serial.timeout = 1
                print ("switch to {} baud".format(new_ascii_baudrate))
                #print (os.popen('stty -F /dev/ehz-hz').read())
                response2 += serial.read()
                #print (response2.decode('iso-8859-9'))
                if len(response2) > 0:
                    if 0x02 in response2:
                        print ("got STX")
                    end = 'false'
                    while end == 'false':
                        response2 += serial.read()
                        #print ("RESPONSE:{0}".format(response2.decode('iso-8859-9')))
                        if (0x03 in response2) and (0x21 in response2):
                            print ("got ETX")
                            end = 'true'
                            print ("FINISH:\n{0}".format(response2.decode('iso-8859-9')))
                            new = 'true'
        except Exception as e:
            print("ERROR: {0}".format(e))
    P.S.: Ich sehe dort einen vermeindlichen Fehler und es funktioniert trotzdem, hab jetzt aber keine Zeit das zu untersuchen, muss einkaufen fahren. Sonst hungere ich das WE .

    EDIT: Fehler beseitigt, tut was es soll.

    @Robert: Wie weiter? Wer machts? Nicht dass wir hier doppelt Zeit verschenken - ich würde es auch zum Finale bringen.

    Einen Kommentar schreiben:


  • Robert
    antwortet
    Zitat von JuMi2006 Beitrag anzeigen
    Hallo Robert,

    ich hab jetzt nochmal 3 Stunden davor gesessen, komme aber auf keinen grünen Zweig. Ich tippe mal auf ein timing-Problem. [...]
    Was noch fehlt ist ein timing nach der Zähler-ID. Kommt dort das ACK zu früh oder zu spät dann wird es im Zweifel ignoriert. Da kann man vielleicht ne Schleife einbauen was aber wegen der unterschiedlichen Geschwindigkeiten schwierig wird. Das ACK muss mit 300baud kommen und dann gehts mit XXXXbaud weiter. Grundsätzlich macht Python3 die Baudratenumstellung aber richtig, zumindest wenn ich nebenbei stty monitore.
    Kunststück - habe die Baudratenumstellung auch ja auf drei verschiedene Arten implementiert - das tuts schon.

    Timing - ein sleep(<optionaler Parameter>) lässt sich ja noch Nachpflegen. Hast du denn mal testweise ein sleep(0.5) eingefügt?

    Genauso evtl. ein Parameter, der das Ack generell abschaltet - wenngleich das ja nun die ganze Geschichte auf 300 baud fixiert.

    Was gut wäre, wären konkrete Fehlerbeschreibungen wie "empfange die Zählerkennung nicht" oder "auf das Ack hin erfolg keine Antwort" oder "Antwort erfolgt trotz Umschaltung mit 300 Baud".

    Einen Kommentar schreiben:


  • fanta2k
    antwortet
    bei mir läufts mit der fixen einstellung auf 300 nun stabil.

    Einen Kommentar schreiben:


  • freetzmaster
    antwortet
    Smartmeter Plugin - Tester gesucht

    Ich denke auch das es ein Timing-Problem ist. Mein Zähler (EMH ITZ) braucht auch nicht zwingend ein ACK.


    Sent from my iPhone using Tapatalk

    Einen Kommentar schreiben:


  • JuMi2006
    antwortet
    Zitat von Robert Beitrag anzeigen
    Würde mich freuen wenn du es mal für deinen Zähler antestest und wir die notwendigen Änderungen dann irgendwie im Ping-Pong-Kreuztest-Verfahren einbauen.

    Grüße
    Robert
    Hallo Robert,

    ich hab jetzt nochmal 3 Stunden davor gesessen, komme aber auf keinen grünen Zweig. Ich tippe mal auf ein timing-Problem. Ich habe 2 Zähler die sich wie folgt verhalten:

    WP:
    Der erwartet ganz normal die Anfrage und lässt einem dann ein wenig Zeit um ein ACK mit Baudrate zu senden. Kommt das nicht plappert der einfach mit 300baud seine Daten raus (dauert 106 Sekunden).

    HZ:
    Der erwartet natürlich auch eine Anfrage und lässt einem dann Zeit für ACK und Baudrate. Kommt kein ACK passiert auch nichts. Der sendet die Daten erst nach dem ACK und Baudrate aus.

    Als nächstes werde ich mal einen etwas russischeren Ansatz testen und das ähnlich dem Perl script machen, aber ohne socat.

    Was noch fehlt ist ein timing nach der Zähler-ID. Kommt dort das ACK zu früh oder zu spät dann wird es im Zweifel ignoriert. Da kann man vielleicht ne Schleife einbauen was aber wegen der unterschiedlichen Geschwindigkeiten schwierig wird. Das ACK muss mit 300baud kommen und dann gehts mit XXXXbaud weiter. Grundsätzlich macht Python3 die Baudratenumstellung aber richtig, zumindest wenn ich nebenbei stty monitore.

    Einen Kommentar schreiben:


  • fanta2k
    antwortet
    hab noch zeile 150 auf

    Code:
    item(float(data[1]), 'DLMS', 'OBIS {}'.format(data[0]))
    geändert.

    Einen Kommentar schreiben:

Lädt...
X