Ankündigung

Einklappen
Keine Ankündigung bisher.

Unterstützung bei Plugin-Entwicklung mit asyncio Loop

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

    Unterstützung bei Plugin-Entwicklung mit asyncio Loop

    Hallo,

    ich bin dabei ein Plugin zur Anbindung meines "tollen" Velux-Dachfenstermotor über das Gateway KLF200 zu schreiben. Erstmal würde mir reichen wenn das Plugin zwei Items unterstützt für das auf und zu fahren. Dabei möchte ich das package pyvlx nutzen, das mit asyncio arbeitet. Und hier liegen auch meine Probleme. Ich habe ein Beispiel-Skript mit dem mein Dachfenster auffährt:

    Code:
    """Just a demo of the new PyVLX module."""
    import asyncio
    
    from pyvlx import Position, PyVLX
    
    
    async def main(loop):
        # Connect
        pyvlx = PyVLX(host="192.168.2.127", password="velux123", loop=loop)
    
        # Runing scenes:
        await pyvlx.load_scenes()
        await pyvlx.scenes["All Windows Closed"].run()
    
        # Changing position of windows:
        await pyvlx.load_nodes()
        await pyvlx.nodes['Bath'].open()
    
        await pyvlx.disconnect()
    
    if __name__ == '__main__':
        # pylint: disable=invalid-name
        LOOP = asyncio.get_event_loop()
        LOOP.run_until_complete(main(LOOP))
        # LOOP.run_forever()
        LOOP.close()
    Hier wird in der main Funktion jetzt ja eine LOOP gestartet. Wie bilde ich das am besten in mein Plugin ab? Starte ich so eine Schleife in der Init Routine?

    Ich habe mal was probiert das funktioniert aber leider nicht:

    Code:
        def __init__(self, sh):
    
            super().__init__()
            self._cycle = 60
            # Initialization code goes here
            self._loop = asyncio.get_event_loop()
            self.init_webinterface(WebInterface)
            return
    Code:
        def run(self):
            self.logger.debug("Run method called")
            self.pyvlx = PyVLX(host="192.168.2.127", password="velux123", loop=self._loop)
            self.alive = True
    Code:
        def update_item(self, item, caller=None, source=None, dest=None):
            if self.alive and caller != self.get_shortname():
                self.logger.info("Update item: {}, item has been changed outside this plugin".format(item.id()))
                if self.has_iattr(item.conf, 'velux_cmd'):
                    self._loop.run_until_complete(self.pyvlx.load_nodes())              
                    self.logger.debug("update_item was called with item '{}' from caller '{}', source '{}' and dest '{}'".format(item,
                                                                                                                   caller, source, dest))
                    if self.get_iattr_value(item.conf, 'velux_cmd') == 'open':
                        if item() == True:
                            self._loop.run_until_complete(self.pyvlx.nodes['Bath'].open())
                pass
    Das führt aber zu dem Fehler:

    2021-07-03 14:42:07 ERROR lib.plugin Plugin 'velux' exception in run() method: There is no current event loop in thread 'velux'.
    Kann mir jemand einen Tipp geben, wie ich die Loop richtig einsetzen müsste?

    #2
    Die Kopplung von asyncio an eine Thread basierte Anwendung wie SmartHomeNG ist nicht trivial.

    Vermutlich wirst Du nicht darum herum kommen, den asyncio basierten Teil an den thread basierten Teil (den Plugin Rahmen) über Queues zu realsieren. Dazu gibt es ein Package mit Namen janus, welches 2 Köpfe hat (daher der Name). Es implementiert Queues, in die ich per asyncio schreiben und aus denen ich thread basiert lesen kann und es implementiert Queues, die genau in die umgekehrte Richtung funktionieren.

    Ich habe janus im Websocket Modul von SmartHomeNG eingesetzt, da das Modul auf dem websockets Package aufsetzt, welches asyncio nutzt.

    Du musst in der run() Methode des Plugins eine asyncio Eventloop starten (und WICHTIG: In der stop() Methode auch wieder beenden).
    Die Kommunikation zwischen den Plugin Methoden und der Eventloop musst Du über die erwähnten Janus Queues abwickeln.
    Viele Grüße
    Martin

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

    Kommentar


      #3
      Danke für deine Antwort.

      Das wird dann kompliziter als gehofft. Ich will es trotzdem gerne versuchen.
      Mein bisheriges Verständnis und die offenen Fragen:

      Ich starte in der run()-Methode über asyncio.new_event_loop() eine neue event loop.

      Dann pushe ich die open oder close Methoden in Update item auf die asynchrone janus-queue?

      Jetzt brauche ich noch einen thread in dem die synchrone queue abgearbeitet wird? Wie setze ich sowas auf?

      Wo ist dann der Zusammenhang zwischen queue und eventloop?

      Ich habe gesehen im AppleTV Plugin von
      Foxi352
      Forums-Einsteiger
      Foxi352 wird auch mit asynio gearbeitet, aber ohne janus. Kann ich mir da evtl. was abschauen?
      android
      Forums-Einsteiger
      Zuletzt geändert von android; 04.07.2021, 08:37.

      Kommentar


        #4
        Nein, Du brauchst keienen extra Thread (das Plugin läuft ja in einem). Du musst nur per Scheduler eine Poll Routine starten (wie im Sample Plugin), um die Daten aus der Janus Que abzuholen.

        Du hast 2 Janus Queues:
        Für das ausgeben von Werten schreibt die update_item() Methode in die eine Queue, welche in der Eventloop gelesen werden muss, um die auszugebenden Daten zu empfangen.

        Eingehende Daten schreib die Eventloop in die zweite Janus Queue, welche von der Poll Methode des Plugins gelesen wird, um die Daten zu verarbeiten und in das entsprechende Item zu schreiben.

        Ich kenne das AppleTV Plugin nicht und kann dazu nichts sagen
        Viele Grüße
        Martin

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

        Kommentar


          #5
          Hallo,

          ich hänge mich hier mal dran.
          Ich würde gerne die Landroid Mähroboter einbinden.
          Das Beispiel (https://github.com/MTrab/pyworxcloud...ple/example.py) nutzt auch asyncio.

          Aber ist das wirklich nötig? Ich meine: Wenn die Plugins ohnehin in einem eigenen Thread arbeiten und das anzubindende Gerät nicht allzu langsam ist...
          Kann das Plugin dann nicht einfach synchron arbeiten/auf das Gerät warten?

          Gruß,
          Hendrik

          Kommentar


            #6
            Das geht, solange Du keine Python Packages einsetzt, die asyncio nutzen. Wenn Du Packages mit Asyncio nutzt, laufen die Plugin-Teile in einem anderen Thread als die asyncio Teile.

            Du musst also entscheiden, ob Du auf ein Package mit asyncio setzt und die nicht unkomplexe Kopplung in Kauf nimmst (wie ich das im Websocket Modul gemacht habe), oder ob Du alternative Packages ohne asyncio suchst bzw. die Funktionalität selber implementierst.
            Viele Grüße
            Martin

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

            Kommentar


              #7
              Zitat von Msinn Beitrag anzeigen
              Du musst nur per Scheduler eine Poll Routine starten (wie im Sample Plugin), um die Daten aus der Janus Que abzuholen.
              Ich blicke es noch nicht ganz. Ich will im ersten Schritt erstmal nur Kommandos ans das Velux Gateway senden. Dann brauche ich nur eine Queue oder? Die in der update_item Methode gefüllt wird. Mir fehlt jetzt aber noch das Verständnis wie dann die Eventloop das Ereignis ausführt.

              Verknüpfe ich queue und loop über diesen Befehl aus der janus Doku einmalig in der run Methode:

              Code:
                  loop = asyncio.get_running_loop()
                  fut = loop.run_in_executor(None, threaded, queue.sync_q)
              Und die Kommands schreibe ich dann über bei update_item rein

              Code:
                  queue.async_q.put('open')
              Irgendwo müsste dann doch noch ein queue get aufgerufen werden oder? Aber ich verstehe nicht wo, das soll ja nicht erst zyklisch erfolgen, sondern direkt mit dem update item

              Kommentar


                #8
                Zitat von android Beitrag anzeigen
                Dann brauche ich nur eine Queue oder?
                Ja

                Zitat von android Beitrag anzeigen
                Mir fehlt jetzt aber noch das Verständnis wie dann die Eventloop das Ereignis ausführt.
                Dann musst Du Dich erstmal in asyncio einlesen.

                Gibt es den keine Möglichkeit die Fenster ohne den Einsatz eines Packages anzusteuern, welches asyncio nutzt? Das würde das Thema erheblich simplifizieren.
                Viele Grüße
                Martin

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

                Kommentar


                  #9
                  Zitat von Msinn Beitrag anzeigen
                  Gibt es den keine Möglichkeit die Fenster ohne den Einsatz eines Packages anzusteuern, welches asyncio nutzt? Das würde das Thema erheblich simplifizieren.
                  Sicherlich. Lese mich auch in die API von dem KLF 200 ein. Eigentlich will ich mit smarthomeng aber auch immer ein bisschen mehr Python lernen und das package hätte dann schon sehr viel Potenzial, wenn ich es einmal verstanden habe. Irgendwann kommen noch ein paar Rollos auf Dachfenstern hinzu.

                  Deshalb würde ich es schon gerne noch versuchen. Dich zu fragen, geht halt auch schneller als alles auf dem harten Weg durch Probieren zu lernen. Wenn dich das nervt oder zu zeitaufwendig ist, dann sag es.

                  Habe mir nochmal asyncio angesehen.

                  Brauch ich die janus queue nur um mehrere Kommandos gleichzeitig abzuarbeiten also wenn ein Kommando rein kommt, wenn das andere noch aktiv ist?

                  Eigentlich müsste es doch für einen ersten Test reichen in der update Routine über

                  Code:
                  asyncio.run(self.pyvlx.nodes['Bath'].open())
                  das Öffnen zu starten oder? Dann müsste ich doch vorher noch nicht einmal eine loop aufsetzen?



                  Kommentar


                    #10
                    Normalerweise arbeiten Programme entweder Thread basiert oder per asyncio. Die Kopplung beider Mechanismen in einem Programm gehört eher zu den höheren Weihen. Das ist sicherlich nicht das beste Umfeld um ein bisschen mehr Python zu lernen.

                    Zitat von android Beitrag anzeigen
                    Brauch ich die janus queue nur um mehrere Kommandos gleichzeitig abzuarbeiten also wenn ein Kommando rein kommt, wenn das andere noch aktiv ist?
                    Wenn Du diese Frage stellst, solltest Du Dir asyncio nochmal ansehen, oder besser Dich in asyncio vollständig einarbeiten. Wenn Du Programme die asyncio basiert sind schreiben kannst, kannst Du Dich an die Kopplung zwischen thread basierten und asyncio basierten Teilen in einem Programm machen.

                    Das auf der Velux Seite dokumentierte API ist da sicherlich der leichtere uns schnellere Weg zu einem stabil arbeitenden Plugin, Zumal es auf der Velux Seite auch Python Beispiele zur Nutzung des API gibt.
                    Viele Grüße
                    Martin

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

                    Kommentar


                      #11
                      Ich würde mich der Empfehlung insofern anschließen, als ich das Entwickeln von Plugins und das Einarbeiten in asyncio erstmal trennen würde.

                      Schau dir doch mal an, wie das Package intern die Fenster anspricht, und setze das in deinem Plugin um.

                      Unabhängig davon kannst du eigenständige asyncio-Programme schreiben.

                      Beides kann man gut unabhängig voneinander machen.

                      Ich habe in der lib.network asyncio und Threads verkoppelt (und wusste leider noch nix von Janus), das ist ... machbar, aber echt unschön. Das Hauptproblem ist, das Timing von Threads und asyncio so abzugleichen, dass er a) nirgendwo hängt, sich b) nicht gegenseitig blovkiert und c) sich extern problemlos steuern (starten/beenden) lässt.
                      Ggf. arbeite ich die lib.network bei Gelegenheit auch nochmal um, der Umweg über eine Queue scheint ein recht eleganter Ausweg zu sein - auf der anderen Seite ist es ein Kompromiss bezüglich Pollfrequenz und Latenz in der Steuerung...

                      Wie Martin schon geschrieben hat - mach nicht alles auf einmal, dann geht auch alles auf einmal schief

                      Kommentar


                        #12
                        Danke für den Input. Bin dabei es ohne asyncio umzusetzen. Läuft auch rudimentär, aber noch nicht stabil. Habe momentan aber nur wenig Zeit es zu vollenden. Werde es hier zum Codereview teilen, wenn es halbwegs stabil läuft.

                        Kommentar


                          #13
                          Moin moin,

                          darf man erfahren, wie hier der stand ist?

                          Gruß Manuel

                          Kommentar


                            #14
                            Hi,

                            leider immer noch nicht weiter gemacht und noch nicht stabil.
                            Problem ist, das die Verbindung zum Gateway nach einiger Zeit abbricht und dann neu aufgebaut werden muss. Mit dem Handling, ob eine Verbindung besteht oder nicht oder nur der login fehlt usw. habe ich aber noch Probleme. Dadurch kommt es dann immer wieder bei Aktionen zu Exceptions im Plugin und die Aktion wird nicht ausgeführt. Laut Doku soll die Verbindung erhalten bleiben, wenn man mindestens alle 15min den Status abfragt, dass funktioniert bei mir aber nicht.

                            Hoffe komme zwischen den Jahren mal dazu weiter zu machen. Werde sonst mal den Code hier hochladen.

                            Kommentar


                              #15
                              Hallo,

                              ich bin weiter gekommen. Läuft nun soweit stabil.

                              Ein "Problem" ist noch, dass bei einem Neustart von smarthomeNG immer die update_item Funktion aufgerufen wird und dann die dem Item value entsprechende Aktion getriggert wird. Das führt dann dazu, dass z.B. ein Kommando zufahren mit einem Positionskommando 70% gleichzeitig kommt. Eigentlich will ich beim Hochlauf gar kein Kommando auswerten. Gibt es dazu einen Standardvorgehen? Das müsste ja eigentlich bei anderen Plugins auch schonmal zu einem Problem geführt haben. Ich habe aber keine entsprechende Implementierung, die das unterbindet, gefunden. Mir käme da jetzt nur ein Timer in den Sinn, der erst nach x Sekunden nach dem Start Kommandos zulässt. Das wäre aber nicht wirklich schön.

                              Kommentar

                              Lädt...
                              X