Ankündigung

Einklappen
Keine Ankündigung bisher.

Problem mit Funktionen und Imports in Logiken

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

    Problem mit Funktionen und Imports in Logiken


    Hallo zusammen,

    ich habe aktuell ein Problem mit Imports und Funktionen in Logiken. Eventuell ist es auch nur ein Verständnisproblem - ich hoffe, ihr könnt mir weiterhelfen.
    Zum Verstehen des Problems habe ich ein kleines Proof of Concept Skript vorbereitet, welches im folgenden "verschlimmbessert" wird, nur damit es als Smarthomeng Logik funktioniert. Die "Versionsunterschiede" in den Skripten habe ich jeweils mit Kommentaren versehen, damit ihr gleich die Unterschiede erkennen könnt.

    Aktuell laufe ich (soweit ich das raus lesen kann) auf SmarthomeNg V 1.3 - also noch eine ältere Version.
    Ich bin leider noch nicht dazu gekommen es upzudaten - falls das Problem also bei neueren Versionen gelöst sein sollte wäre das mal ein Grund dazu... Eventuell kann ja jemand mit einer aktuellen Version das Skript ausprobieren, falls das nicht ganz sicher ist.


    Version 1 des Poc Skriptes:
    So funktioniert es theoretisch, wenn ich es einfach mit Python starte (also ausserhalb von Smarthomeng)
    HTML-Code:
    #/usr/local/smarthome/logics/poc.py
    #!/usr/bin/env python
    
    from datetime import datetime #import von datetime
    
    def func1():
        today = datetime.today()
        sh.telegram(str(today),True) #Ausgabe von Today als String
    
    def func2():
        func1() #Aufruf von func1
    
    try:
        func2() #Aufruf von func2
    except Exception as e:
        sh.telegram(str(e),True) #Fehlerausgabe
    Kurze Erklärung:
    Func1 holt sich aus datetime das heutige Datum und gibt es via sh.telegram Plugin einem Telegram Bot aus. Ich verwende hier sh.telegram mal als Synonym für die Python eigene print Funktion. Geht hier also lediglich um die Textausgabe!
    Func2 ruft Func1 auf.
    Im Try Block wird Func2 aufgerufen, bei einem Fehler bekomme ich die Fehlermeldung als Text ausgegeben.

    Dieses Skript funktioniert nun also in der normalen cli, wenn ich es mit Python starte (wenn sh.telegram = print!)

    Als Smarthomeng Logik funktioniert es nicht. Stattdessen bekomme ich folgenden Fehler:
    HTML-Code:
    name 'func1' is not defined
    Smarthomeng erkennt also die Func1 nicht im Func2 Kontext!

    Version 2:
    Ich gebe nun Func1 als Parameter für Func2 mit, damit Func2 Func1 kennt

    HTML-Code:
    #/usr/local/smarthome/logics/poc.py
    #!/usr/bin/env python
    
    from datetime import datetime
    
    def func1():
        today = datetime.today()
        sh.telegram(str(today),True)
    
    def func2(f): #func2 bekommt nun eine Funktion als Parameter
        f() #und ruft diese Funktion auf
    
    try:
        func2(func1) #hier wird func1 als Parameter mitgegeben. Denn hier kennt man func1
    except Exception as e:
        sh.telegram(str(e),True)
    Ergebnis:
    HTML-Code:
    'module' object has no attribute 'today'
    Immer noch ein Fehler. Func1 kennt nun datetime nicht. Das Import Statement wird also auch nicht mit in den Kontext der Funktion übernommen.

    Version 3:
    Nun gebe ich den Import in der Funktion mit. Das würde dafür sorgen, dass er plötzlich das sh Objekt nicht kennt (da ich das bisher auch nicht mitgegeben habe). Deshalb ziehe ich auch das sh Objekt via Parameter in den Kontext der Funktionen.

    HTML-Code:
    #/usr/local/smarthome/logics/poc.py
    #!/usr/bin/env python
    
    def func1(sh): #sh wird mitgegeben
        from datetime import datetime #import wurde in func1 gezogen
        today = datetime.today()
        sh.telegram(str(today),True)
    
    def func2(f,sh): #funktion und sh werden mitgegeben
        f(sh)
    
    try:
        func2(func1,sh) #funktion und sh werden mitgegeben
    except Exception as e:
        sh.telegram(str(e),True)
    Ergebnis:
    HTML-Code:
    2020-02-04 10:36:04.380120
    Erst jetzt bekomme ich via Telegram Bot die richtige Text Ausgabe.


    Nun meine Frage.
    Gibt es einen besseren Weg, Funktionen und Imports in den Kontext von Funktionen zu ziehen?
    Wenn ich ein großes Skriopt mit vielen Funktionen habe, die innerhalb von Funktionen aufgerufen werden (die auch innerhalb von Funktionen aufgerufen werden usw.)
    Dann müsste ich immer via Parameterangabe die ganzen Funktionen mit geben, nur damit sie innerhalb von Funktionen genutzt werden können!
    Man erkennt schon am Satz, dass das ein großer Sche** ist...

    Da kann ich ja komplett auf Funktionen verzichten und einfach eine Prozedur schreiben - wenn ich den gleichen Code nochmal brauche mach ich halt Copy&Paste.

    Also, hat jemand schon Erfahrungen damit gemacht und eine saubere Lösung für Imports und Funktionen in Funktionen - in Logiken?

    Merci, ich hoffe, ich konnte das Problem einigermaßen verständlich erklären.
    Zuletzt geändert von Loki; 04.02.2020, 13:59.

    #2
    OK, jetzt habe ichs gelesen....
    Hatte hier erst voreilig etwas geschrieben....

    Sorry
    Zuletzt geändert von schuma; 04.02.2020, 12:18.

    Kommentar


      #3
      Das Verhalten ist durch Python bestimmt. Logiken sind keine Python Module. Es gibt innerhalb der Logik keinen "globalen" Namensraum.

      Variablen, die direkt in der Logik definiert werden, sind daher auch keine globalen Variablen. Auf sie kann aus Funktionen innerhalb der Logik nicht zugegriffen werden. Sollen solche Werte in Funtionen genutzt werden, müssen sie als Parameter an die Logik übergeben werden.

      Analog müsstest Du func1 als Parameter an func2 übergeben, um func1 innerhalb von func2 aufrufen zu können.

      Da dieses Verhalten direkt durch Python vorgegeben ist, ändert es sich auch nicht, wenn Du neuere Versionen als v1.3 von SmartHomeNG einsetzt.


      Es gäbe einen Weg, der Dir evtl. helfen könnte (aber wohl erst in neueren Versionen):

      Schau mal in die Doku unter https://www-smarthomeng.de/user. Dort sind Variablen beschrieben, den den Lauf einer Logik überstehen. Diese werden im Kontext logic definiert. Diesen Kontext kannst Du auch für Funktionen nutzen:

      Code:
      def func1():
          logger.warning("function 1")
      
      def func2(logic):
          logger.warning("function 2")
          logic.func()
      
      logic.func = func1
          func2(logic)
      - Du weist die func1 einer Variablen im Kontext Logik zu (im Beispiel logic.func, der Name ist frei wählbar. Du kannst sie der Übersichtlichkeit halber auch logic.func1 nennen)
      - Du übergibst den Kontext logic als Parameter an func2
      - Nun kannst Du in func2 auf alle Variablen im Kontext logic zugreifen
      - Du rufst logic.func() auf

      Das funktioniert zumindest unter SmartHomeNG v1.6 genau so.
      Viele Grüße
      Martin

      There is no cloud. It's only someone else's computer.

      Kommentar


        #4
        Da bei diesem Vorgehen jeder Funktion derselbe Parameter mit dem selben Wert übergeben werden muss, erfolgt das am einfachsten, indem dieses als letzter Parameter mit Standardwert erfolgt. Dann braucht bei den Aufrufen der Funktionen der Parameter nicht berücksichtigt zu werden.

        Um das Ganze einheitlich zu machen, sollte das für alle Funktionen so gemacht werden, auch wenn diese nicht aus anderen Funktionen aufgerufen werden:

        Code:
        # Definition der Funktionen
        def func1(val, logic=logic):
            logger.warning("function 1")
            logger.warning("value {}".format(val))
        
        def func2(logic=logic):
            logger.warning("function 2")
            logic.func1(2)
        
        logic.func1 = func1
        logic.func2 = func2
        
        # Code der Logik
        logic.func1(1)
        logic.func2()
        Zuletzt geändert von Msinn; 07.02.2020, 16:08.
        Viele Grüße
        Martin

        There is no cloud. It's only someone else's computer.

        Kommentar


          #5
          Loki Das ist wirklich ein Problem was sich nicht in den Griff bekommen läßt. Ich habe damals Marcus auch gefragt und als einzige Quintessenz des ganzen war:

          Schreib' Dir ein eigenes Plugin und stelle die Funktionen die Du brauchst per Plugin bereit.

          Alternativ kann man auch folgendes machen:

          Code:
          #/usr/local/smarthome/logics/poc.py
          #!/usr/bin/env python
          
          from datetime import datetime
          import sys
          
          class Funktionen():
              def __init__(self, locs):
                  for k in locs:
                      setattr(self, k, locs[k])
                  
              def func1(self, what): #sh wird mitgegeben
                  today = self.datetime.today()
                  #self.sh.telegram(str(today),True)
                  print("Foobar",what)
          
          f = Funktionen({'sh': sh, 'datetime': datetime})
          
          f.func1("foo")
          Du kannst das via executor Plugin (develop) ausprobieren.

          Kommentar


            #6
            bmx Mit dem was ich genau über Deinem Post gepastet habe, lässt sich das relativ umfassent lösen. Einzige Ergänzung: Du musst auch dir sh Variable durch das logic objekt führen: logic.sh = sh und in den Funktionen halt statt sh dann logic.sh verwenden.

            Das war in den frühen Versionen (zu Macus Zeiten) ohne das logic Objekt natürlich umständlicher.
            Viele Grüße
            Martin

            There is no cloud. It's only someone else's computer.

            Kommentar


              #7
              Msinn, vielen Dank - ich denke, das ist die wahrscheinlich sinnvollste Lösung. Muss ich hierfür das logic Objekt nehmen, oder könnte ich auch alle Funtkionen und Imports separat als Parameter mit Standardwert übergeben?
              Gibt es Nachteile, wenn ich das logic Objekt benutze? (Auch in Verbindung mit anderen Logiken?) Oder ist für jede Logik und ihr Aufruf das Logik-Objekt ein separates, welches nach dem Aufruf der Logik aufhört zu existieren?

              Zitat von Msinn Beitrag anzeigen
              logic1.func1 = func1
              logic2.func2 = func2
              "logic1 und logic2" <- ist das hier ein Schreibfehler? Ich vermute mal, sonst verwirrt mich der Code an der Stelle nämlich

              Kommentar


                #8
                Du könntest auch alle Funktionen und Werte als Parameter übergeben. Das kann aber schnell unübersichtlich werden. Außerdem musst Du beachten, dass Variablen, die Du als Parameter übergibst sich je Typ unterschiedlich verhalten können. Bei einfachen Variablen (string, integer, ...) wird der Wert übergeben wenn dieser in der Funktion verändert wird, wird das nicht in die Original Variable übertragen. Diese bleibt unverändert. Wenn Du allerdings z.B. ein dict oder ein Objekt übergibst, wird dieses per Referenz übergeben. Das bedeutet, in der Funktion vorgenommene Änderungen werden direkt auf dem Original Objekt ausgeführt.

                Die Übergabe aller Parameter einzeln hat den Nachteil, dass Du daran denken musst für einen Parameter, den Du in func1 benötigst, diesen auch dem Aufruf von func2 hinzuzufügen, weil Du ihn sonst beim Aufruf von func1 aus func2 heraus nicht übergeben kannst. --> Je verschachtelter, deto komplexer und fehleranfälliger.

                Wenn Du alles in das Logik Objekt packst, hast Du überall das gleiche Verhalten. So als hättest Du in der Funktion eine globale Variable angesprochen.

                Nachteile in Verbindung mit anderen Logiken gibt es nicht, da jede Logik ihr eigenes privates logic Objekt hat. Da logic Objekt hört NICHT auf zu existieren. Der Sinn liegt gerade darin, Variablen von einem Lauf der Logik zum nächsten Lauf zu "retten". Das logic Objekt hört erst auf zu existieren, wenn Du im Admin Interface die Logik entlädst.


                logic1 und logic2 sind Schreibfehler. Das muss in beiden Fällen logic heißen.
                Zuletzt geändert von Msinn; 07.02.2020, 16:11.
                Viele Grüße
                Martin

                There is no cloud. It's only someone else's computer.

                Kommentar


                  #9
                  Vielen Dank noch mal für die Erklärung.
                  Hat jetzt alles wunderbar funktioniert.

                  Kommentar


                    #10
                    Magst das finale Ergebnis hier noch posten Loki ?

                    Kommentar


                      #11
                      Im fertigen Ergebnis mache ich nichts anderes, als von Msinn erwähnt. Schlussendlich habe ich einige Funktionen, die wiederum Funktionen aufrufen usw. Die Logik selbst nutze ich, um die Wagenreihung und Verspätung von einem speziellen Zug abzufragen - hierfür benutze ich mehrere DB APIs, die ich zusammenführe.

                      Das Ergebnis sieht dann ungefähr so aus - ich habe nur die Logik selbst entfernt, da es hier nicht darum geht.

                      Code:
                      import urllib.request
                      import json
                      from datetime import date,datetime
                      
                      def getArrivalDeparture(data):
                          pass
                      
                      def getChangedTimes(logic=logic):
                         pass
                      
                      def getString(jsonobj, logic=logic):
                          pass
                      
                      def getCorrectTime(jsonobj,today):
                          pass
                      
                      def getAndSendZugdata(urlpart2, logic=logic):
                          pass
                      
                      logic.getString = getString
                      logic.getChangedTimes = getChangedTimes
                      logic.getArrivalDeparture = getArrivalDeparture
                      
                      today = date.today()
                      try:
                          msg = getAndSendZugdata("endpunkt")
                          sh.telegram(msg,True)
                      except Exception as e:
                          sh.telegram("Unknown Error:\n"+str(e),True)
                      Der "Einstiegspunkt" ist getAndSendZugdata -> hier werden dann bei bedarf weitere Funktionen aufgerufen, die Schlussendlich im logic Objekt zu finden sind (zum Beispiel getString, getChangedTimes, etc.)

                      Kommentar


                        #12
                        Hallo,
                        ich muss mich hier mal dranhängen da ich ein ähnliches Problem habe.
                        folgede Logik habe ich geschrieben und diese Funktioniert auch ausserhalb von SH.
                        Code:
                        #!/usr/bin/env python3
                        # wallboxpv.py
                        import logging
                        import requests
                        import json
                        
                        #Parameter
                        wb_url = "http://192.168.3.204/"
                        pv_url = "http://192.168.3.201/solar_api/v1/GetPowerFlowRealtimeData.fcgi"
                        
                        def setWBstatestate):
                        url = wb_url + "setStatus"
                        r_wb_cur = requests.get(url, params=[('active',state )])
                        if (state == "true" and r_wb_cur.status_code == 200):
                        print('WB aktiviert')
                        if (state == "false" and r_wb_cur.status_code == 200):
                        print('WB deaktiviert')
                        
                        def change_current(direction):
                        wb_actualCurrent = getWBcurrent()
                        wb_newcurrent = 0
                        url = wb_url + "setCurrent"
                        if direction == "up":
                        wb_newcurrent = wb_actualCurrent + 1;
                        
                        elif direction == "down":
                        wb_newcurrent = wb_actualCurrent - 1;
                        
                        else:
                        wb_newcurrent = wb_actualCurrent;
                        
                        if wb_newcurrent < 7:
                        setWBstate("false")
                        elif wb_newcurrent > 16:
                        print("Current: "), wb_actualCurrent ,("A mehr geht nicht")
                        else:
                        r_wb_cur = requests.get(url, params=[('current', wb_newcurrent)])
                        print("Current: ") , wb_actualCurrent , ("A - neu: ") , wb_newcurrent
                        
                        def getPVsoc():
                        r_wb = requests.get(pv_url)
                        data_wb = r_wb.json()
                        data = data_wb['Body']['Data']['Inverters']['1']['SOC']
                        #data = 4180
                        return data
                        
                        
                        def getWBdata():
                        url = wb_url + "getParameters"
                        r_wb = requests.get(url)
                        data_wb = r_wb.json()
                        data = (data_wb['list'][0]['actualPower']) * 1000
                        #data = 4180
                        return data
                        
                        def getPVdata():
                        r_pv = requests.get(pv_url)
                        data_pv = r_pv.json()
                        data = data_pv['Body']['Data']['Site']['P_Grid']
                        #data = -100
                        return data
                        
                        def getWBcurrent():
                        url = wb_url + "getParameters"
                        r_wb = requests.get(url)
                        data_wb = r_wb.json()
                        data = data_wb['list'][0]['actualCurrent']
                        return data
                        
                        #auslesen Einspeisung
                        pv_P_Grid = getPVdata()
                        print("PV_Grid: "), pv_P_Grid
                        
                        #Auslesen aktuelle Ladeleistung
                        wb_actualPower = getWBdata()
                        print("WB_P: "), wb_actualPower
                        
                        pv_soc = getPVsoc()
                        print("PV_SOC: "), pv_soc
                        
                        #Logic
                        if pv_P_Grid < 0:
                        if wb_actualPower > 0:
                        if (pv_P_Grid) < -250:
                        change_current("up")
                        elif wb_actualPower == 0:
                        if pv_P_Grid < -1620:
                        setWBstate("true")
                        else:
                        if wb_actualPower > 0:
                        change_current("down")
                        if (pv_soc) < 60:
                        setWBstate("false")
                        Wenn ich das ganze via SH auslösse bekomme ich folgenden Fehler im Log:
                        Code:
                        2020-07-19 10:18:04 ERROR logics.WallboxPV Logic: logics.WallboxPV, File: /usr/local/smarthome/logics/wallboxpv.py, Line: 57, Method: getPVdata, Exception: name 'requests' is not defined
                        > Traceback (most recent call last):
                        > File "/usr/local/smarthome/lib/scheduler.py", line 527, in _task
                        > exec(obj.bytecode)
                        > File "/usr/local/smarthome/logics/wallboxpv.py", line 71, in <module>
                        > pv_P_Grid = getPVdata()
                        > File "/usr/local/smarthome/logics/wallboxpv.py", line 57, in getPVdata
                        > r_pv = requests.get(pv_url)
                        > NameError: name 'requests' is not defined
                        Ich habe auch schon versucht die import anweisung mit in die einzelnen Funktionen rein zu nehmen aber leider mit dem gleichen Ergebniss.

                        Könnt Ihr bitte mal drüber schauen wo der Fehler sein könnte?
                        Danke

                        Kommentar


                          #13
                          Du solltest Dir mal den entsprechenden Abschnitt der Doku zu Gemüte führen: https://www.smarthomeng.de/user/logi...von-funktionen
                          Viele Grüße
                          Martin

                          There is no cloud. It's only someone else's computer.

                          Kommentar


                            #14
                            Und das hier: https://knx-user-forum.de/forum/supp...-einf%C3%BCgen

                            Und
                            Code:
                            def setWBstatestate):
                            wird wohl eher auch außerhalb von shng nicht funktionieren..

                            Kommentar


                              #15
                              Danke Martin,
                              manchmal sieht man dem Wald vor lauter Bäumen nicht.
                              @ Onkelady ja da hatte ich ausversehen die ( gelöscht. Ist aber schon repariert. Aber Danke für den Hinweis.


                              VG
                              Tobias

                              Kommentar

                              Lädt...
                              X