Ankündigung

Einklappen
Keine Ankündigung bisher.

- √ - Mein Plugin verhindert manchmal sauberes beenden von smarthome.py

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

    - √ - Mein Plugin verhindert manchmal sauberes beenden von smarthome.py

    Hallo,

    ich habe ein Plugin zum interpretieren von EMH Stromzählerdaten geschrieben. Die Daten werden über die serielle Schnittstelle gelesen.
    Wird dieses Plugin genutzt kann ich zu 30% smarthome.py aus dem interaktiven Mode nicht mit CTRL+C beenden. Die Shell des interaktiven Modes friert dann ein und die sh Instanz läuft fröhlich weiter. Auch ein kill <PID> hilft dann nicht. Erst ein kill -9 <PID> beendet die Instanz. Dann wird die Shell in der der interactive Modus gelaufen ist auch wieder frei. Manchmal sind danach die Terminaleinstellungen verstellt (kein Keyboard echo mehr, keine Newline bei enter etc).

    Ganz selten bekomme ich diese Fehlermeldung:
    Code:
    >>> Unhandled exception in thread started by <bound method Plugin._bootstrap of <Plugin(ehz, stopped 139903929153280)>>
    Aufgrund dieser Probleme gibt es dieses Plugin auch noch nicht im GIT - da ich selbst im Dev Branach nichts veröffentlichen möchte was die Stabilität des ganzen Systems beeinträchtigen könnte.

    Anbei der Quellcode von dem Plugin:
    Code:
    #!/usr/bin/env python3
    # vim: set encoding=utf-8 tabstop=4 softtabstop=4 shiftwidth=4 expandtab
    #########################################################################
    # Copyright 2013 KNX-User-Forum e.V.            https://knx-user-forum.de/
    #########################################################################
    #  This file is part of SmartHome.py.    http://mknx.github.io/smarthome/
    #
    #  SmartHome.py 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.
    #
    #  SmartHome.py 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 SmartHome.py. If not, see <http://www.gnu.org/licenses/>.
    #########################################################################
    
    import logging
    import serial
    import struct
    
    logger = logging.getLogger('ehz_emh')
    
    class ehz_emh():
        def __init__(self, smarthome, port = '/dev/ttyUSB0', baudrate = 9600, parity = serial.PARITY_NONE, stopbits = serial.STOPBITS_ONE, bytesize = serial.EIGHTBITS, \
                     timeout = .1, length_emh_frame = 392, length_tarif_data = 23, length_tarif_value = 5):
            self._sh = smarthome
            self._tarif = {}
            self._port = port
            self._baudrate = baudrate
            self._parity = parity
            self._stopbits = stopbits
            self._bytesize = bytesize
            self._timeout = timeout
            self._length_emh_frame = length_emh_frame
    #        self._rx_buffer = rx_buffer
            self._length_tarif_data = length_tarif_data
            self._length_tarif_value = length_tarif_value
            self._ser = serial.Serial(port = self._port, baudrate = self._baudrate, parity = self._parity, stopbits = self._stopbits, \
                                      bytesize = self._bytesize, timeout = self._timeout)
            if self._ser.isOpen():
                logger.info("eHZ_EMH is connected to {0}".format(self._ser.portstr))
            else:
                logger.error("Can not connect to {0}".format(self._ser.portstr))
    
            
        def run(self):
            self.alive = True
            self._ser.flushInput()
            while self.alive:
                if not self._ser.isOpen():
                    self._ser = serial.Serial(port = self._port, baudrate = self._baudrate, parity = self._parity, stopbits = self._stopbits, \
                                              bytesize = self._bytesize, timeout = self._timeout)
                    logger.info("Reconnect needed, now connected to {0}".format(self._ser.portstr))
                dataset = self._ser.read(self._length_emh_frame)
                if len(dataset) == 0:
    #                logger.debug("No eHZ Data received from port ".format(self._ser.portstr))
                    continue
                if len(dataset) != self._length_emh_frame:
                    logger.warning("Invalid eHZ Data. Len received: {0}   Len expected: {1}".format(len(dataset), self._length_emh_frame))
                    self._ser.flushInput()
                    continue
    #            print (type(dataset))
    #            print (dataset)
    #            for line in dataset:
    #                print("{0:02X}".format(line), end = " ")
    #            print("")
                crc = self._sml_crc16(dataset)
                if [dataset[-2],dataset[-1]] == [crc[-2],crc[-1]]:
                    pass
    #                logger.debug("CRC OK")
                else:
                    logger.warning("CRC NOK")
                    continue
                for tarif in self._tarif:
                    value = self._get_tarif(dataset, tarif)
                    self._tarif[tarif](value)
                
        def stop(self):
            self.alive = False
            self._ser.close()
    
        def parse_item(self, item):
            if 'ehz_emh_tarif' in item.conf:
                tarif = item.conf['ehz_emh_tarif']
                logger.debug("parse item: {0}".format(item))
                if tarif not in self._tarif:
                    self._tarif[tarif] = item
                else:
                    self._tarif[tarif].append(item)
            return None
    
        def parse_logic(self, logic):
            pass
    
        def update_item(self, item, caller=None, source=None, dest=None):
            if caller != 'plugin':
                logger.info("update item: {0}".format(item.id()))
    
    
    # CRC Berechnung mit Lookuptable
        def _sml_crc16(self, cp):
            crctab = [0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf, 0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7, \
                      0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e, 0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876, \
                      0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd, 0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5, \
                      0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c, 0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974, \
                      0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb, 0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3, \
                      0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a, 0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72, \
                      0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9, 0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1, \
                      0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738, 0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70, \
                      0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7, 0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff, \
                      0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036, 0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e, \
                      0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5, 0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd, \
                      0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134, 0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c, \
                      0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3, 0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb, \
                      0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232, 0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a, \
                      0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1, 0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9, \
                      0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330, 0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78]
            fcs = 0xFFFF
            index = 0
            length = len(cp) - 2
            while (length):
                length -= 1
                fcs = (fcs >> 8) ^ crctab[(fcs ^ cp[index]) & 0xff]
                index += 1
            fcs ^= 0xffff
            return struct.pack('H',fcs)
    
        def _get_tarif(self, dataset, tarif):
            result1 = None
            result2 = None
    # Auf Anfangssequenz von ZählerstandBlock <tarif> parsen und diesen Block in result1 vorhalten    
            search = bytearray.fromhex('77 07 01 00')
            for n in tarif:
                search.append(int(n))
            search.append(0xFF)
            cnt = dataset.find(search)
            if cnt >= 0:
                result1 = dataset[cnt:cnt+self._length_tarif_data]    
            if result1 is None:
                logger.info("Tarif dataset {0} not Found!".format(tarif))
                return
    
    #   Auf Anfangssequenz von Zäherstand parsen und diesen in result2 vorhalten
            search = bytearray.fromhex('62 1E 52 FF 56')
            length = len(search)
            cnt = result1.find(search)
            if cnt >= 0:
                result2 = result1[cnt+length:cnt+length+self._length_tarif_value]
            if result2 is None:
                logger.info("Tarif value {0} not Found!".format(tarif))
                return
    
    # Zäherstand konvertieren hex->int
            num = 0
            for data in result2:
                num <<= 8
                num |= data
            return num / 10000
    
    
    if __name__ == '__main__':
        logging.basicConfig(level=logging.DEBUG)
        myplugin = Plugin('ehz_emh')
        myplugin.run()
    Wäre super wenn jemand eine Idee hätte wie das beenden sauber umgesetzt werden kann.

    LG

    Mode

    #2
    Hallo Daniel,

    bitte lade das Plugin in Dein github repo und poste den Link.

    Dort kann ich den Code einfach kommentieren.

    Bis bald

    Marcus


    Gesendet von unterwegs

    Kommentar


      #3
      Hi Daniel,

      ich habe mir mal den Code ein bisschen angeschaut.

      Auf Anhieb kann ich keinen Fehler erkennen. Ich würde mal einen Debug-Output unter die run while self.alive Schleife schreiben um zu erkennen ob er da rausgeht. Oder einfach mehr Debug-Output einbauen um mitzubekommen was er macht.

      Bis bald

      Marcus

      Kommentar


        #4
        Super, mach ich im nächsten Jahr.

        Silvesterbesuch steht gleich vor der Tür... dieses Jahr wird 2 mal gefeiert ;-)


        Euch allen einen guten Rutsch!

        Kommentar


          #5
          Hallo Marcus,

          habe heute mal deinen Rat befolgt und debug Ausgaben eingebaut. Dabei ist aufgefallen dass die Methode stop() von meinem Plugin nicht aufgerufen wird, wenn smarthome.py beendet wird. Somit wird alive niemals false und das Beenden ruckelt.

          Wie kann das sein? Hat das mit der seriellen Schnittstelle zu tun? Ich denke ja, denn mein EHZ Plugin ist das einzige Plugin, was sich ständig im Status befindet. Alle anderen stehen meist auf stopped.

          Ich habe zum weiteren Test in der plugin.py in Methode stop() in der Schleife über die Plugins eine Logausgabe gemacht.
          Code:
              def stop(self):
                  logger.info('Stop Plugins')
                  for plugin in self._threads:
                      logger.info('Stop Plugin: {0}'.format(plugin))
                      plugin.stop()
          Code:
          2014-01-19 23:51:09,567 INFO     Main         Number of Threads: 9 -- smarthome.py:stop:351
          2014-01-19 23:51:09,567 INFO     Main         Stop Plugins -- plugin.py:stop:70
          2014-01-19 23:51:09,568 INFO     Main         Stop Plugin: <Plugin(knx, stopped 140411540600576)> -- plugin.py:stop:72
          2014-01-19 23:51:09,568 INFO     Main         Stop Plugin: <Plugin(nw, stopped 140411532207872)> -- plugin.py:stop:72
          2014-01-19 23:51:09,568 INFO     Main         Stop Plugin: <Plugin(cli, stopped 140411532207872)> -- plugin.py:stop:72
          2014-01-19 23:51:09,568 INFO     Main         Stop Plugin: <Plugin(dmx1, stopped 140411532207872)> -- plugin.py:stop:72
          2014-01-19 23:51:09,569 INFO     Main         Stop Plugin: <Plugin(bc, stopped 140411532207872)> -- plugin.py:stop:72
          2014-01-19 23:51:09,569 INFO     Main         Stop Plugin: <Plugin(visu, stopped 140411532207872)> -- plugin.py:stop:72
          2014-01-19 23:51:09,569 INFO     Main         Stop Plugin: <Plugin(solarlog, stopped 140411540600576)> -- plugin.py:stop:72
          2014-01-19 23:51:09,569 INFO     Main         Stop Plugin: <Plugin(ehz, started 140411521664768)> -- plugin.py:stop:72
          Aber im stop() vom ehz Plugin kommt er nie an, vermutlich weil das Plugin immer im Status startet ist.

          Kannst du damit was anfangen?

          LG

          Mode

          Kommentar


            #6
            Hi Daniel,

            was veranlasst Dich zu der Vermutung die stop Methode wird nicht aufgerufen? Hast Du dort auch Debug Output eingebaut?

            Ich denke er hängt in Deiner while Schleife, vllt da Du in stop die Serielle schließt.

            Bis bald

            Marcus


            Gesendet von unterwegs

            Kommentar


              #7
              Hallo Marcus,

              die Stop Methode in meinem Plugin sieht wie folgt aus:
              Code:
                  def stop(self):
                      logger.info("KILLING"+self.alive)
                      self.alive = False
                      self._ser.close()
              Das KILLING wird niemals im Log ausgegeben. Obwohl die Methode wie oben gezeigt von plugin.py stop() aufgerufen wird.

              In der Main Schleife des Plugins gebe ich self.alive aus. Dort sieht man dass self.alive auch beim Beenden von Smarthome.py noch true ist.

              LG

              Kommentar


                #8
                Hi Daniel,

                das loggen verhindert das korrekte ausführen der Methode. Du versuchst einen String mit einem Bool zu verknüpfen, das gibt eine Exception. Diese wird abgefangen aber nicht ausgegeben.

                Probier doch mal:
                Code:
                    def stop(self):
                        logger.info("KILLING 1")
                        self.alive = False
                        try:
                             self._ser.close()
                        except Exception as e:
                             logger.exception(e)
                        logger.info("KILLING 2")
                Bis bald

                Marcus

                Kommentar


                  #9
                  Hallöle,

                  also ich sehe da ne Stelle die ein Problem bereiten kann, wenn es zu einer Situation kommt, in der nur ein einzelnes Zeichen eingelesen wird

                  Code:
                          length = len(cp) - 2
                          while (length):
                              length -= 1
                  Wenn ich das (ohne serielle Schnitte) nachstelle, startet er bei angenommener Länge von 1 für cp mit ffffffff und läuft und läuft....bis der PI absäuft ;-)

                  Auf jeden Fall würde ich die Länge von cp (dataset) auf >1 prüfen
                  Greetings, Torsten

                  Kommentar


                    #10
                    Hallo Marcus,

                    autsch, das ist peinlich. Hast aber vollkommen recht. Jetzt läuft alles wie geschmiert. Beenden ist kein Problem mehr.
                    Allein das sqlite Plugin braucht einige Sekunden bis zum Beenden. Aber was wird wohl am Schließen der DB Files liegen.
                    Daher fände ich folgende logger.debug Anweisung in plugin.py ganz nett. Dann kann man direkt sehen, falls ein Plugin beim Beenden Ärger macht oder einfach nur länger braucht:

                    Code:
                        def stop(self):
                            logger.info('Stop Plugins')
                            for plugin in self._threads:
                                logger.debug('Stop Plugin: {0}'.format(plugin))
                                plugin.stop()
                    @Todro
                    Da hast du Recht. Mein Stromzähler sendet aber immer nur Datenpakete mit fester Länge. Die Länge wird vor dem Aufruf der CRC Methode überprüft. Sprich die Methode wird niemals mit nur einem Zeichen aufgerufen. Aber danke für deine Idee :-)

                    Code:
                                if len(dataset) != self._length_emh_frame:
                                    logger.warning("Invalid eHZ Data. Len received: {0}   Len expected: {1}".format(len(dataset), self._length_emh_frame))
                                    self._ser.flushInput()
                                    continue
                    VG

                    Mode

                    Kommentar


                      #11
                      Hi Daniel,

                      ich habe noch ein bisschen debug output spendiert.

                      Bis bald

                      Marcus

                      Kommentar


                        #12
                        Hallo Daniel,
                        Halo Marcus,

                        wurde gerne das Plugin einsetzen und testen.
                        Leider finde ich es auf github repo nicht.

                        Kann mir jemand das Aktuelle Plugin senden,
                        bzw. sagen wo ich es herrunterladen kann.

                        Danke

                        Kommentar


                          #13
                          Hallo,

                          ich hätte auch Interesse!

                          Gruß

                          Nils

                          Kommentar

                          Lädt...
                          X