Ankündigung

Einklappen
Keine Ankündigung bisher.

Pluginentwicklung, Trigger von serieller Schnittstelle

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

    Pluginentwicklung, Trigger von serieller Schnittstelle

    Hallo,

    ich möchte ein Plugin für meinen EHZ (Stromzähler mit IR Schnittstelle) schreiben.
    Der Zähler gibt seinen Zählerstand und andere Daten ca. 1 mal die Sekunde aus. Diese Daten würde ich gerne im Plugin parsen und auf Items verteilen.

    Nur wie bekomme ich das Plugin aufgerufen, immer wenn an der seriellen Schnittstelle neue Daten anliegen? Der Workaround das Plugin jede Sekunde zu triggern und mit einem nicht blockierenden read() zu arbeiten gefällt mir nicht wirklich.


    LG

    Mode

    #2
    Pluginentwicklung, Trigger von serieller Schnittstelle

    Da gibt es doch schon Plugins für EHZ, hast du mal geschaut wie es dort implementiert ist?
    Mit freundlichen Grüßen
    Niko Will

    Logiken und Schnittstelle zu anderen Systemen: smarthome.py - Visualisierung: smartVISU
    - Gira TS3 - iPhone & iPad - Mobotix T24 - ekey - Denon 2313 - Russound C5 (RIO over TCP Plugin) -

    Kommentar


      #3
      Du meinst das DLMS Plugin? Hier wird eine andere bidirektionale Schnittstelle verwendet. Sprich smarthome.py fragt beim Zähler die Werte an und der Zähler antwortet.
      Die Verbindung zu meinem Zähler ist unidirektional und der Zähler gibt ca alle 1,5 Sekunden seine Daten raus.

      Kommentar


        #4
        Hallo Daniel,

        das mit dem Triggern erzeugt unnötig Last. Ich würde das Plugin/den Thread selber loopen lassen und mit select/poll arbeiten.

        Ich habe mir aber überlegt, ob ich serielle Schnittstellen etwas generischer über lib.connection einbinde. Mal sehen.

        Bis bald

        Marcus

        Kommentar


          #5
          Hallo Marcus,

          ich habe am Wochenende mit poll experimentiert. Dabei rausgekommen, dass die Systemlast mit einem blockierendem read() mit Timeout 1s noch geringer ist als mit mit poll(). Möchte daher vorerst so arbeiten.

          Mein Plugin in dem ich die Daten vom EHz lese funktioniert auch schon. Ich bin mir aber nicht sicher, ob das Plugin sich beim beenden von sh.py korrekt verhält.

          Code:
              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, max_bytes = 500):
                  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._max_bytes = max_bytes
                  self._ser = serial.Serial(port = self._port, baudrate = self._baudrate, parity = self._parity, stopbits = self._stopbits, bytesize = self._bytesize, timeout = self._timeout)
                  logger.info("EHZ_EMH is connected to {0}".format(self._ser.portstr))
              def run(self):
                  self.alive = True
                  while self.alive:
                      self._ser.flushInput()
                      dataset = self._ser.read(self._max_bytes)
                      [...] weitere Verarbeitung und relevante items setzen
          
              def stop(self):
                  self.alive = False
                  self._ser.close()
          Ist das so ok?


          Edit:
          Gerade einer von vielen Stops nicht erfolgreich:

          Console
          Code:
          >>> Unhandled exception in thread started by <bound method Plugin._bootstrap of <Plugin(ehz, stopped 140047022003968)>>
          Log
          Code:
          2013-11-17 00:29:02,754 INFO     Main         Stop Plugins -- plugin.py:stop:70
          2013-11-17 00:29:02,758 DEBUG    Main         KNX: closing socket cgate.ts:6720 -- connection.py:close:302
          2013-11-17 00:29:02,762 ERROR    Dummy-7      Unhandled exception: 'utf-8' codec can't decode byte 0xe4 in position 2805: invalid continuation byte
          <class 'UnicodeDecodeError'>
            File "/usr/lib/python3.2/threading.py", line 713, in _bootstrap
              self._bootstrap_inner()
            File "/usr/lib/python3.2/threading.py", line 753, in _bootstrap_inner
              (self.name, _format_exc()))
            File "/usr/lib/python3.2/traceback.py", line 269, in format_exc
              format_exception(etype, value, tb, limit, chain))
            File "/usr/lib/python3.2/traceback.py", line 186, in format_exception
              list.extend(format_tb(tb, limit))
            File "/usr/lib/python3.2/traceback.py", line 75, in format_tb
              return format_list(extract_tb(tb, limit))
            File "/usr/lib/python3.2/traceback.py", line 100, in extract_tb
              line = linecache.getline(filename, lineno, f.f_globals)
            File "/usr/lib/python3.2/linecache.py", line 15, in getline
              lines = getlines(filename, module_globals)
            File "/usr/lib/python3.2/linecache.py", line 41, in getlines
              return updatecache(filename, module_globals)
            File "/usr/lib/python3.2/linecache.py", line 132, in updatecache
              lines = fp.readlines()
            File "/usr/lib/python3.2/codecs.py", line 300, in decode
              (result, consumed) = self._buffer_decode(data, self.errors, final)
           -- smarthome.py:_excepthook:494
          2013-11-17 00:29:03,226 INFO     Main         Thread: Main, still alive -- smarthome.py:stop:370
          2013-11-17 00:29:03,227 INFO     Main         Thread: Dummy-7, still alive -- smarthome.py:stop:370

          Kommentar


            #6
            Hi Daniel,

            ja das ist ok so.

            Zu Deiner Exception kann ich nichts sagen, es fehlt der Code-Kontext.

            Du solltest aber mehr try: except: einbauen. U.a. um das close()

            Bis bald

            Marcus

            Kommentar


              #7
              Hallo zusammen!

              Wollte kurz checken, ob sich in punkto serieller Schnittstelle schon was getan hat oder noch tun wird.

              Ich möchte eine Verbindung mit einem Arduino herstellen. Letzterer soll bei einer Änderung der Daten diese an Smarthome.py schicken. Derzeit löse ich das über ein adaptiertes Roomla-Plugin, das jede Sekunde cyclet. Und somit auch immer beim Arduino nach den Daten frägt, der dann zurücksendet.

              Das funktioniert zwar, ist aber sicher nicht die optimale Lösung. CPU-Last von Python ist dadurch 2,3% statt 0,7% und Memory ist bei 4,3%. Soweit kein Drama, aber doch klar ein Unterschied. Die TCP-Verbindung mit meinen anderen laufenden Plugins (adaptiertes Denon-Plugin, etc.) funktioniert einwandfrei ohne zusätzliche Last..

              Kommentar


                #8
                Hallo,

                Zitat von Onkelandy Beitrag anzeigen
                Wollte kurz checken, ob sich in punkto serieller Schnittstelle schon was getan hat oder noch tun wird.
                Nein und Ja.

                Bis bald

                Marcus

                Kommentar


                  #9
                  Danke für die Info. Wie gesagt löse ich die Sache derzeit so, dass jede Sekunde vom Arduino Daten geholt werden. Hat sich dort nichts geändert, gibt's aber auch keine neuen Info. Dadurch sollte die Kommunikation halbwegs im Zaum gehalten werden.

                  Dennoch gibt's immer wieder folgende Fehlermeldung:
                  Code:
                  2014-06-30 21:44:35 ERROR    Main         Connection polling failed: [Errno 4] Unterbrechung während des Betriebssystemaufrufs
                  Traceback (most recent call last):
                    File "/usr/local/smarthome/bin/smarthome.py", line 357, in start
                      self.connections.poll()
                    File "/usr/local/smarthome/lib/connection.py", line 101, in poll
                      for fileno, event in self._epoll.poll(timeout=1):
                  IOError: [Errno 4] Unterbrechung während des Betriebssystemaufrufs
                  2014-06-30 21:44:38 ERROR    Main         Connection polling failed: [Errno 4] Unterbrechung während des Betriebssystemaufrufs
                  Traceback (most recent call last):
                    File "/usr/local/smarthome/bin/smarthome.py", line 357, in start
                      self.connections.poll()
                    File "/usr/local/smarthome/lib/connection.py", line 101, in poll
                      for fileno, event in self._epoll.poll(timeout=1):
                  IOError: [Errno 4] Unterbrechung während des Betriebssystemaufrufs
                  Irgendeine Idee? Könnte das evtl. auch von nem anderen Plugin kommen? wie könnte ich da mehr Infos dazu raus kriegen..?

                  Dankeschööön!

                  Kommentar


                    #10
                    Hallo,

                    Du kannst mehr Debug-Output in die lib/connection.py einbauen um den Fehler einzugrenzen. Ich vermute aber das Problem bei Deinem Plugin/Hardware.

                    Bis bald

                    Marcus

                    Kommentar


                      #11
                      Hallo,
                      ich habe das gleiche Problem. Ich bastle gerade an einem Plugin für die NIBE Wärmepumpe. Es funktioniert auch soweit. Da ich permanent die serielle Schnittstelle abhören muss (es prasseln unaufhörlich Daten rein die ich filtere) ist meine Frage wie ich das am besten im Plugin einbaue. Ich brauche ja ein Thread der die Daten ausliest und einen der die Daten in die Items schaufelt oder ein Thread welcher beides macht.

                      Das Plugin, wie ich verstehe läuft im sh als Thread, darf die run Methode des Plugins blockieren? D.h wenn ich da die schleife mit dem serielle Schnittstelle Auslesen reinlege, so wie mode
                      es gemacht hat.

                      Was passiert wenn ich item(value, ...) aufrufe? Werden dabei viele Daten verarbeitet. Ich frage weil sehr viele Daten reinkommen in schnellen Intervallen. Wenn item(value,...) viel macht würde es Sinn machen die Daten lokal zu behalten und über den sheduler in kleineren Intervallen in Items zu schaufeln.

                      LG
                      eddso

                      Kommentar


                        #12
                        Hallo,

                        ein Plugin darf seinen Thread behalten/blockieren. Anderes geht es momentan mit einer seriellen auch nicht.

                        Keine Ahnung was bei Dir passiert wenn Du die Items änderst. Prinzipiell ist das Backend ziemlich schnell. Die Frage ist wieso willst Du die Änderungen "reinpumpen". Ich würde das zeitlich wahrscheinlich entspannen, da ja auch Logiken durch Änderungen getriggert werden können.

                        Bis bald

                        Marcus

                        Kommentar


                          #13
                          Vielen dank für die Antwort. Ich habe mir die Zeit genommen den ganzen sh Code rein zuziehen um zu verstehen wie alles Funktioniert. Ist einiges klar geworden. Schönes teil hast du da gebaut. Hier mein Plugin für die NIBE SPLIT WP. Ich aktualisiere die Items nur dann wenn sich was ändert. Einige Werte ändern sich relativ oft (Druck, Temperaturen und der WP) aber ich denke das ist ok.
                          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/
                          #########################################################################
                          #  NIBE 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 serial
                          import re
                          import termios
                          from struct import *
                          
                          logger = logging.getLogger('NIBE')
                          
                          class NIBE():
                              def __init__(self, smarthome, serialport):
                                  self._sh = smarthome
                                  self._nibe_regs = {}
                                  self._serial = serial.Serial(serialport, 19200, bytesize=serial.EIGHTBITS, stopbits=serial.STOPBITS_ONE, timeout=2)
                                  iflag, oflag, cflag, lflag, ispeed, ospeed, cc = termios.tcgetattr(self._serial)
                                  CMSPAR = 0x40000000
                                  cflag |= termios.PARENB | CMSPAR | termios.PARODD # to select MARK parity
                                  termios.tcsetattr(self._serial, termios.TCSANOW, [iflag, oflag, cflag, lflag, ispeed, ospeed, cc])
                                    
                              def run(self):
                                  self.alive = True
                                  try:
                                      while self.alive:
                                          if bytes(self._serial.read(1))[0] != 0x03:
                                              continue
                                          if bytes(self._serial.read(1))[0] != 0x00:
                                              continue
                                          if bytes(self._serial.read(1))[0] != 0x14:
                                              continue
                                          self._serial.write(b"\x06")
                                          self._serial.drainOutput()
                                          self._serial.flushInput()
                                          
                                          frm = bytes()
                                          frm += self._serial.read(1) #<C0>
                                          if frm[0] == 0x03:
                                              continue
                                          
                                          frm += self._serial.read(3) #<00> <59> <len>
                                          l = int(frm[3])
                                          frm += self._serial.read(l+1)
                                          
                                          self._serial.write(b"\x06")
                                          self._serial.drainOutput()
                                          self._serial.flushInput()
                                          
                                          crc = 0
                                          for i in frm[:-1]:
                                              crc ^= i
                                          if crc != frm[-1]:
                                              logger.warning("frame crc error")
                                              continue
                                          
                                          msg = frm[4:-1]
                                          l = len(msg)
                                          i=4
                                          while i <= l:
                                              reg = msg[i-3]
                                              if i != l and (msg[i] == 0x00 or i == (l-1)):
                                                  raw = bytes([msg[i-2],msg[i-1]])
                                                  i+=4
                                              else:
                                                  raw = bytes([msg[i-2]])
                                                  i+=3
                          
                                              if not reg in self._nibe_regs:
                                                  continue
                                              
                                              if self._nibe_regs[reg]['raw'] == raw:
                                                  continue
                                                  
                                              value = self._decode(reg, raw)
                                              logger.debug("update_item: reg:{0} = {1}".format(reg,value))
                                              self._nibe_regs[reg]['raw'] = raw
                                              for item in self._nibe_regs[reg]['items']:
                                                  item(value, 'NIBE', 'REG {}'.format(reg))
                                                  
                                  except Exception as e:
                                      logger.warning("nibe: {0}".format(e))
                          
                              def stop(self):
                                  self.alive = False
                                  self._serial.close()
                                  
                              def parse_item(self, item):
                                  if 'nibe_reg' in item.conf:
                                      logger.debug("parse item: {0}".format(item))
                                      nibe_reg = int(item.conf['nibe_reg'])
                                      if not nibe_reg in self._nibe_regs:
                                          self._nibe_regs[nibe_reg] = {'items': [item], 'logics': [], 'raw':0}
                                      else:
                                          self._nibe_regs[nibe_reg]['items'].append(item)
                                  return None
                              
                              def _decode(self, reg, raw):
                                  if len(raw) == 2:
                                      value = unpack('>H',raw)[0]
                                  else:
                                      value = unpack('B',raw)[0]
                                      
                                  if reg in [0,32,33,34,35,36,38,44,45,46,48,100,101,102,103,104,105]:    
                                      #0    CPUID
                                      #32   Zusatzheizung erlaubt   
                                      #33   Max dF Commpressor   
                                      #34   Verd. Freq. regP   
                                      #35   Min Startzeit Freq min
                                      #36   Minzeit konst. Freq min
                                      #38   Verd. Freq. GradMin   
                                      #44   Pumpengeschwindigkeit %
                                      #45   Bw reg P   
                                      #46   Bw reg Q   
                                      #48   Bw reg Wert xP %
                                      #100  Datum - Jahr
                                      #101  Datum - Monat
                                      #102  Datum - Tag
                                      #103  Uhrzeit - Stunde
                                      #104  Uhrzeit - Minute
                                      #105  Uhrzeit - Sekunden
                                      return int(value)
                                      
                                  if reg == 31:
                                      #31   Status Heizung
                                      #1 Auto
                                      #3 Heizung
                                      #5 Brauchwasser
                                      #6 Zusatzheizung
                                      return int(value)
                                      
                                  if reg in [4,8]: #signed
                                      #4    Heizkurvenverschiebung
                                      #8    Gradminuten
                                      return int(unpack('h',pack('H',value))[0]/10)
                                      
                                  if reg == 25: #unsigned
                                      #25   Verdichterstarts
                                      return int(value/10)
                                      
                                  if reg in [1,5,6,7,11,12,13,14,15,16,17,18,21,23,27,37]: #signed
                                      #1    Aussentemp °C        
                                      #5    Vorlauf Soll °C
                                      #6    Vorlauf Ist °C
                                      #7    Ruecklauf °C
                                      #11   Kondensator aus (MAX) °C
                                      #12   Brauchwasser oben °C
                                      #13   Brauchwasser unten °C
                                      #14   Verd. Temp. Tho-R1 °C
                                      #15   Verd. Temp. Tho-R2 °C
                                      #16   Sauggas Temp. Tho-S °C
                                      #17   Heissgas Temp. Tho-D °C
                                      #18   Fluessigkeitstemp AMS °C
                                      #21   Atemp. am AMS Tho-A °C
                                      #23   Invertertemp. Tho-IP °C
                                      #27   Vorlauf °C
                                      #37   Max Diff. soll-ber °C
                                      return float(unpack('h',pack('H',value))[0]/10)
                                      
                                  if reg in [9,10,19,20,22,24]: #unsigned
                                       #9    Verd. Freq. Soll Hz
                                      #10   Verd. Freq. Ist Hz
                                      #19   Hochdruck bar
                                      #20   Niederdruck bar
                                      #22   AMS Phase Ist A
                                      #24   Verdichterlaufzeit h
                                      return float(value/10)
                                      
                                  if reg in [40,47]: #unsigned
                                      #40   Hysterese °C
                                      #47   Bw reg xP
                                      return float(value/2)
                                      
                                  if reg in [43,49,50]:
                                      #43   Stopptemp. Heizen °C
                                      #49   Brauchwasser StartTemp °C 1.2
                                      #50   Brauchwasser StopTemp °C  1.3
                                      return float(value)
                                  
                                  #2    ?
                                  #3    ?
                                  #26   ?
                                  #28   ?
                                  #29   ?
                                  #30   ?
                                  #39   ?
                                  #41   ?
                                  #42   ?
                                  return value
                          Über Verbesserungen freue ich mich
                          LG
                          Eduard

                          Kommentar


                            #14
                            Sehr schöne Sache.. da kann man schon den einen oder anderen Ansatz herausholen.

                            Wäre dennoch genial, wenn du mal in einer langweiligen Stunde noch ein paar Kommentare einfügen könntest. Dann wär's einfacher, die Sache generischer zu gestalten

                            Danke vielmals!

                            Kommentar


                              #15
                              Hallo Eduard,

                              schönes Plugin. Auf den ersten Blick ist mir nichts aufgefallen.

                              Über ein Readme und einen Pull-Request (https://github.com/mknx/smarthome/tr...v#start-coding) würde ich mich freuen.

                              Bis bald

                              Marcus

                              Kommentar

                              Lädt...
                              X