Ankündigung

Einklappen
Keine Ankündigung bisher.

Sonos Plugin Python 3.10 und ping

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

    Sonos Plugin Python 3.10 und ping

    Hi

    Ich habe kürzlich angefangen das sonos plugin für mich zu nutzen.
    Erstmal vielen Dank an die/denjenigen der sich die Mühe gemacht hat und es uns zur Verfügung stellt! Sehr coole Sache!

    Zwei Kleinigkeiten sind mir bisher aufgefallen die ich gerne kurz besprechen würde.
    Mit python 3.10 mag das Plugin gar nicht ohne folgende Änderung:
    Code:
    diff --git a/sonos/utils.py b/sonos/utils.py
    index f50e8ddf..df656209 100755
    --- a/sonos/utils.py
    +++ b/sonos/utils.py
    @@ -2,7 +2,7 @@ import hashlib
     import os
     import socket
     import re
    -from collections import Set
    +from collections.abc import Set
    
    
     def is_valid_port(port):
    Und bei mir darf der smarthome user kein "ping" ausführen, abgesehen davon lässt meine firewall per default keine icmp requests durch.
    Daher hab ich noch folgende Änderung durchgeführt:
    Code:
    diff --git a/sonos/__init__.py b/sonos/__init__.py
    index 03e4d9f5..e03347d9 100755
    --- a/sonos/__init__.py
    +++ b/sonos/__init__.py
    @@ -2899,7 +2899,7 @@ class Sonos(SmartPlugin):
                     # we try to ping the speaker
                     with open(os.devnull, 'w') as DEVNULL:
                         try:
    -                        subprocess.check_call(['ping', '-i', '0.2', '-c', '2', zone.ip_address],
    +                        subprocess.check_call(['nc', '-nzw1', zone.ip_address, '1400'],
                                                   stdout=DEVNULL, stderr=DEVNULL, timeout=1)
                             is_up = True
                         except subprocess.CalledProcessError:
    das funktioniert natürlich nur wenn netcat installiert ist
    # Arch Linux
    Code:
    pacman -S gnu-netcat
    # Debian + derivate
    Code:
    apt install netcat
    und damit ist auch diese variante nicht optimal, aber funktioniert in meinem fall weil ich netcat onehin auf meinen systemen installiert habe.
    Leider fehlt mir das python wissen um eine python interne netcat implementierung vorschlagen (eine zusätzliche pip anforderung nur dafür ist vermutlich ähnlich unschön wie ein zusätzliche Paketanforderung).
    Alternativ fällt mir ein statt nur auf TCP level zu sehen ob der speaker online ist wirklich z.b. ab zu fragen ob das LED gerade an oder aus ist. Damit wäre dann auch sicher gestellt dass die Gegenstelle wirklich eine Sonos Box ist und nicht etwas anderes was zufällig auf Port 1400 lauscht.

    Was denkt ihr?
    Zuletzt geändert von drbone; 02.03.2022, 11:51.

    #2
    Hi drbone ,

    der Hinweis bzgl. "import Set" ist gut und einfach abwärtskomptabel zu integrieren. Ich sehe aber gerade keine Funktion in der utils.py, die diesen Import wirklich benötigt. Kannst Du den Import erstmal vollständig auskommentieren und schauen, ob der überhaupt noch nötig ist?

    Zweites Thema: Das Konstrukt mit "ping" hat der damalige plugin Autor hinzugefügt, da die native discovery funktion von SoCo nicht immer zuverlässig erkannt hat, welche Speaker verfügbar sind. SoCo wurde hier kontinuierlich weiterentwickelt. Evtl. ist das zusätzliche pingen jetzt gar nicht mehr nötig. Mir fehlt aktuell nur die Zeit und Motivation, hier herumzuprobieren. Evtl. willst Du Dich hier tiefer einarbeiten.
    In der Zwischenzeit müsstest Du hier mit Deiner Speziallösung leben.


    Kommentar


      #3
      Hi aschwith

      Danke für deine Antwort und kein Problem, ich hatte nicht erwartet dass jemand anfängt meine Probleme zu lösen. Bin Dankbar für Antworten und Hinweise, mir Fehlt einfach das Pyhton Wissen. Zeit habe ich zwar auch keine aber motiviert bin ich. Gleich vorweg; sorry für unausweichliche Dummheiten 😅

      Kannst du dich noch erinnern warum das damals ein Problem war, dass SoCo nicht erkannt hat welche Speaker verfügbar sind? Also wie hat sich das geäußert?

      Ich bin gestern dann auf noch ein Problem gestoßen mit dem ich gerade kämpfe:
      Es scheint als würde das plugin diverse Felder nicht lesen können. Z.b. track_uri oder auch player_name ist immer leer (die meisten str sind leer) aber z.b. household_id und sonos_playlists ist befüllt. Ich versuche noch heraus zu finden woran das liegt.

      Auch verstehe ich noch nicht ganz wie das Plugin mitbekommt wenn z.b. jemand über die app auf "play/pause" gedrückt hat und den "status"/"rückmeldewert" erhält.
      Aktuelles Ziel ist: Per KNX taster würde ich gerne zumindest play/pause toggeln können, aber halt wenn möglich ohne 2x drücken zu müssen falls jemand über die app dazwischen gefunkt hat.


      EDIT: Ok, gerade rausgefunden woran das liegt. Wenn ich smarthome temporär ins iot vlan (wo auch sonos ist) verschiebe sind alle bisher überprüften werte befüllt und auch die play/pause Erkennung funktioniert.
      Zuletzt geändert von drbone; 03.03.2022, 09:19.

      Kommentar


        #4
        Hi

        update von meiner Seite: ich hab meine Firewall ersetzt (wollte ich onehin tun) die neue kann broadcast relays. Jetzt können die dienste in den dafür vorgesehenen vlans bleiben und damit funktioniert der erste Speaker! Ich kann mittels KNX taster play/pause drücken und wenn jemand über die app dazwischen funkt klappt das auch 🎉.
        Hintergrund: Sonos schickt status updates per broadcast die natürlich nicht routed werden.

        Leider steh ich damit vor einem neuen Problem:
        Zum testen habe ich natürlich nur mit einem Speaker getestet, jetzt habe ich versucht die anderen in die Liste auf zu nehmen und habe bemerkt dass immer nur die erste IP in der liste (im etc/plugins.yaml) funktioniert. Egal welchen meiner Speaker ich in der liste als ersten Eintrag angebe funktioniert, die anderen nicht.
        Hat jemand eine Idee woran das liegen könnte? Ich hab schon ein bisschen im Code gesucht, bin aber bisher nicht fündig geworden.

        Kommentar


          #5
          Bei mir funktioniert das statische setzen der IPs über die etc/plugins.yaml.

          Wie sieht bei Dir der Sonos Teil der plugin.yaml aus?

          Was sagt das smarthomeNG log wenn Du einen Speaker ansprichst, der "nicht funktioniert"?

          Kommentar


            #6
            Wenn ich bei einem nicht funktionierenden player "play" drücke kommt "not initialized"

            So sieht mein etc/plugin.yaml aus
            Code:
            Sonos:
                class_name: Sonos
                class_path: plugins.sonos
                speaker_ips:
                  - ipA
                  - ipB
                  - ipC
                  - ipD
                  - ipE
                  - ipF
            Ich bin inzwischen etwas weiter als heute Nachmittag.
            Rausgefunden habe ich dass er in der "discover(self) -> None:" Methode/Funktion alle 6 speaker in "zones" bekommt und damit bis zur "for zone in zones" schleife kommt.

            Ab jetzt wirds interpretation: Ich denke dass er innerhalb der for loop irgendwo (immer noch beim ersten item der liste) stirbt bzw hängen bleibt was dann dazu führt dass eben der erste speaker geht und die anderen nicht. Dann wurde ich unterbrochen :-)

            Sidenote: python 3.10.2 es könnte sein dass das Problem damit verknüpft ist, aber es könnte auch sein dass sich in der for loop noch etwas versteckt was sich mit meinem netzwerksetup nicht verträgt.

            Kommentar


              #7
              ok, deine etc/plugin.yaml ist korrekt. Ich meine, Du brauchst das Attribut class_path nicht. Aber die speaker_ips habe ich bei mir testweise eben auch so eingetragen. Es funktioniert bei mir mit Python 3.8.6. Vielleicht ist das Problem bei Dir wirklich die neuere Python Version 3.10. Denn trotz statischer IP Konfiguration wird jeder Speaker nochmal angepingt. Dahder musst Du mindestens einen Aufruf des periodischen "discovers" abwarten. Vorher sind die Speaker definitiv nicht initialisiert. Beim Aufruf des ping gibt es anscheinend auch bei bestimmten Python Versionen Probleme. Hierfür gibt es einen Pullrequest im Develop, den Du mal ausprobieren könntest.

              Siehe hier: https://github.com/smarthomeNG/plugins/pull/593

              Kommentar


                #8
                Entweder class_pass und class_name angeben oder einfach nur plugin_name. Beides geht.
                Viele Grüße
                Martin

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

                Kommentar


                  #9
                  Danke euch beiden!

                  Aktueller Stand: Es funktioniert überall wo nur 1 Sonos Box ist. Dass sich der "Master" von einem Rechts/Links Paar ändert habe ich in meiner Automatisierung noch nicht berücksichtigt. D.h. ob ein Raum mit 2 Boxen geht oder nicht ist aktuell noch ein Glücksspiel, aber das schau ich mir an wenn es mir wieder besser geht.
                  Frage an dieser Stelle: Wie sollte man das denn am besten Abbilden? Ich kann ja nur eine UID pro Sonos item angeben, oder?

                  Der PullRequest den du erwähnt hast sollte meines Erachtens so nicht gemerged werden. So wie er jetzt ist kann man den ganzen try block auch einfach weg lassen.
                  "ping -i 0.2 -c 2" wartet ewig wenn ein ziel nicht erreichbar ist und damit läuft er ins "Timeout=1". Ich gehe davon aus dass der "except subprocess.TimeoutExpired:" das auffangen sollte, tut er aber nicht. Warum? Dafür reicht mein Python Wissen leider nicht aus. Abgesehen davon bin ich wie gesagt onehin kein Fan von ping weil das impliziert dass die Boxen und shng im gleichen vlan laufen bzw eine zusätzliche Regel in der Firewall braucht falls nicht.

                  Was wars am Ende? Das "If zone.uid is None:"
                  Nachdem er die uid zu dem Zeitpunkt noch nicht hat fragt er danach, dieser call failed natürlich für einen offline speaker und damit stirbt er und der die restlichen speaker werden nie gefragt.

                  Wie hab ichs jetzt umgebaut?
                  Code:
                  @@ -2889,25 +2889,22 @@ class Sonos(SmartPlugin):
                           self.zero_zone = False
                  
                           for zone in zones:
                  -            if zone.uid is None:
                  +            self.logger.warning("Pinging speaker with ip  {ip}".format(ip=zone.ip_address))
                  +            # don't trust the discover function, offline speakers can be cached
                  +            # we try to ping the speaker
                  +            try:
                  +                proc_result = subprocess.run(['nc', '-nzw1', zone.ip_address, '1400'],
                  +                                             stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=1)
                  +                is_up = True
                  +            except subprocess.CalledProcessError:
                  +                self.logger.debug("Debug: Ping process finished with return code {0}".format(proc_result.returncode))
                  +                is_up = False
                  +            except subprocess.TimeoutExpired:
                  +                self.logger.debug("Debug: Ping process timed out")
                                   is_up = False
                  -            else:
                  -                uid = zone.uid.lower()
                  -
                  -                self.logger.debug("Pinging speaker with uid {0}".format(uid))
                  -                # don't trust the discover function, offline speakers can be cached
                  -                # we try to ping the speaker
                  -                with open(os.devnull, 'w') as DEVNULL:
                  -                    try:
                  -                        subprocess.check_call(['ping', '-i', '0.2', '-c', '2', zone.ip_address],
                  -                                              stdout=DEVNULL, stderr=DEVNULL, timeout=1)
                  -                        is_up = True
                  -                    except subprocess.CalledProcessError:
                  -                        is_up = False
                  -                    except subprocess.TimeoutExpired:
                  -                        is_up = False
                  
                               if is_up:
                  +                uid = zone.uid.lower()
                                   self.logger.info("Debug: Speaker found: {zone}, {uid}".format(zone=zone.ip_address, uid=uid))
                                   online_speaker_count = online_speaker_count + 1
                                   if uid in sonos_speaker:
                  Wobei man sich den TimeoutExpired sparen kann da "nc" mit dem -w1 onehin nach 1 sekunde failed und er in den "CalledProcessError" reingeht.
                  Ich fände immer eine version die ohne "nc" oder "ping" mit python hausmitteln auskommt am besten.

                  Kommentar


                    #10
                    Ich habe bei mir keine Sonos Speaker als Stereopaar konfiguriert und kann daher Dein Problem nicht direkt nachstellen. Kann das Plugin in dem Fall Stereopaar nicht die beiden Einzellautsprecher auflösen?

                    Ich werde Deine Änderung oben (nur das spätere Lesen der uid) bei mir bei Gelegenheit testen und bei Rückwirkungsfreiheit übernehmen.

                    Der Pluginautor pfischi hat damals bewusst noch ein "ping" auf Netzwerkeben hinzugefügt, da beim Verlust einer Netzwerkwerbindung die native discovery Funktion überdurchschnittlich lange benötigt, den entsprechenden Speaker auch als Offline zu erkennen. Die Speaker werden sehr lange noch gecacht. Daher der Grund für ein anschließendes Pingen der potentiell Speaker, die online sind.

                    Ein Umstieg auf python Bordmittel ist prinzipiell möglich, müsstest Du aber implementieren, da ich hierfür keinen unmittelbaren Grund sehe. Bei einer Änderung müsstest Du auch den Zweig mit Nutzung der soco.discover Funktion im Test mit abdecken. Dieser wird von den meisten Usern hier genutzt. Ich empfehle dafür noch die SoCo Doku. Ich meine, irgendwann wurde die Offline Erkennung noch mit einer zusätzlichen soco Funktion erweitert. Vielleicht kann man diese direkt übernehmen.

                    Viele Grüße

                    Kommentar


                      #11
                      Hallo drbone ,

                      so, ich hatte endlich die Zeit, mir Deinen Änderungsvorschlag tiefer anzuschauen. Deinen Vorschlag kann ich leider so nicht übernehmen. Zwei Gründe:

                      1) Grundsätzliches: Für eine Steuerung der Sonos Speaker ist zwingend die ID (=UID) des jeweiligen Speakers notwendig. Diese wird ausschließlich über die SoCo Funktion discover() identifiziert: Entweder über eine echte discovery oder über eine discovery von bekannten Speaker IPs. Das bedeutet, wenn bei Dir für ein Speaker zone.uid None ist, konnte der Speaker richtig identifiziert werden. Alle weiteren Schritte sind dann sinnlos. Falls der Zustand bei Dir mehrere Discovery Zyklen andauern sollte, müsstet Du die soco.discovery Funktion im Zusammenhang mit Deiner doch speziellen Netzwerkkonfiguration debuggen.

                      Das anschließende Pingen hat der damalige Plugin Autor ausschließlich zum Abfangen von "false positives" eingebaut. Sprich, um Speaker als aktuell offline zu klassifizieren, die fälschlicher Weise (aufgrund von Caching) immer noch als online angezeigt werden. Sinn und Zweck des Pingen kann und wird nicht sein, Speaker, die aufgrund der Discovery Funktion als offline angezeigt werden, noch irgendwie online zu bekommen. Es würden einfach die wichtigen Attribute wie z.B. die Speaker UID fehlen.

                      2) Zweiter sehr banale Grund ist, dass Du das Auslesen der UID in den Zweig "if is_up:" verschoben hast. Auch der Zugehörige else Zwei benötigt die UID Information, weshabl Deine Änderung ein Fehler schmeißt, sobald der else Pfad genutzt wird.

                      Gruß
                      Alex

                      Kommentar


                        #12
                        Hallo Alex

                        Danke dir für deine Mühen! Mein code ist noch nicht optimal, soviel ist denke ich offensichtlich.
                        Ich denke aber so ganz ohne Änderung sollte der aktuelle Code nicht bleiben.


                        Zitat von aschwith Beitrag anzeigen
                        1) Grundsätzliches: Für eine Steuerung der Sonos Speaker ist zwingend die ID (=UID) des jeweiligen Speakers notwendig. Diese wird ausschließlich über die SoCo Funktion discover() identifiziert: Entweder über eine echte discovery oder über eine discovery von bekannten Speaker IPs. Das bedeutet, wenn bei Dir für ein Speaker zone.uid None ist, konnte der Speaker richtig identifiziert werden. Alle weiteren Schritte sind dann sinnlos. Falls der Zustand bei Dir mehrere Discovery Zyklen andauern sollte, müsstet Du die soco.discovery Funktion im Zusammenhang mit Deiner doch speziellen Netzwerkkonfiguration debuggen.
                        Die uid ist/war leider nie "None", also zumindest hat das if nicht funktioniert. Wenn das if funktioniert hätte, hätte ich kein Problem gehabt.
                        Der use case war: "shng hat in der liste eine IP, die erstens noch nie initialisiert war und zweitens offline ist"
                        Das hat zur Folge dass er bei besagtem "If zone.uid is None" einfach stirbt/hängen bleibt und die restlichen IPs (die oline wären) nie prüft.
                        Wie gesagt fehlt mir das python wissen um zu verstehen warum das so ist, meine Erklärung war dass er erst an genau dieser Stelle versucht die UID vom speaker zu erfragen und dabei stirbt/hängen bleibt weil der speaker eben offline ist (no route to host).
                        Für speaker die online waren hat es übrigens funktioniert, und das sofort.

                        Generell:
                        IOT Geräte mittels Firewall etwas in ihren Möglichkeiten ein zu schränken halte ich für eine gute Idee und würde ich nicht als "speziell" ansehen. Ich kann aber gut damit leben falls du das anders siehst und ich denke nicht dass es Sinnvoll ist darüber zu diskutieren.

                        Zitat von aschwith Beitrag anzeigen
                        Das anschließende Pingen hat der damalige Plugin Autor ausschließlich zum Abfangen von "false positives" eingebaut. Sprich, um Speaker als aktuell offline zu klassifizieren, die fälschlicher Weise (aufgrund von Caching) immer noch als online angezeigt werden. Sinn und Zweck des Pingen kann und wird nicht sein, Speaker, die aufgrund der Discovery Funktion als offline angezeigt werden, noch irgendwie online zu bekommen. Es würden einfach die wichtigen Attribute wie z.B. die Speaker UID fehlen.
                        Das war auch nie der Zweck der Übung. Wie oben geschrieben habe ich versucht den "ping" vor irgend welche soco calls zu bekommen damit er sich eben nicht aufhängt/daran verschluckt wenn der speaker offline ist. Den Fall dass der ping ging, aber die discovery nicht hatte ich glaub ich nie, eher umgekehrt weil meine firewall 1400 durchlies aber ping nicht.

                        Zitat von aschwith Beitrag anzeigen
                        2) Zweiter sehr banale Grund ist, dass Du das Auslesen der UID in den Zweig "if is_up:" verschoben hast. Auch der Zugehörige else Zwei benötigt die UID Information, weshabl Deine Änderung ein Fehler schmeißt, sobald der else Pfad genutzt wird.
                        Aber hat der aktuelle code dann nicht genau das gleiche "banale" Problem?
                        Code:
                        if zone.uid is None:
                            is_up = False
                        Viele Wege führen nach Rom und so ist es denke ich auch bei diesem Problem.
                        Ob man erst feststellt ob die IP erreichbar ist bevor man die uid auflöst oder das eventuelle scheitern der uid auflösung abfängt oder sogar die Möglichkeit gibt die UID in form einer map zur IP in die config zu schreiben ist schlussendlich egal.

                        Falls du das alles anders siehst und lieber den code nicht ändern willst ist das voll ok für mich und wir können das Thema hier abschließen.

                        Kommentar


                          #13
                          Hallo drbone ,

                          ich sehe gerade noch keinen Grund für eine Änderung. Wo ich Dich gerne unterstütze, ist das Thema "aufhängen/verschlucken" weiter zu debuggen. Das Sollverhalten des aktuellen Codes ist wie folgt:

                          1) Es wird über alle verfügbaren Sonos Speaker in einer For- Schleife iteriert.
                          2) Für jeden Speaker wird überprüft, ob UID bestimmt werden konnte und anschließend ob der Speaker auch wirklich über das Netzwerk erreichbar ist. Über die Reihenfolge haben wir uns oben schon ausgetauscht. Ich sehe keinen Grund, hier was anzupassen.
                          3) Falls bei den Überprüfungen in 2) irgendeine Bedingung nicht erfüllt wird, wird der Speaker übersprungen. Er ist dann halt nicht für das Sonos Plugin verfügar. Es werden aber alle weitere Speaker in der Schleife weiter abgehandelt.

                          Du müsstest genau sagen, was sich bei Dir "aufhängt". Dazu müsstest Du eine detaillierte Beschreibung inklusive Logfile mitbringen. GGf. bitte nach den einzelnen Schritten in der For-schleife noch weitere logs einfügen, damit Du siehst, wo genau was fehlschlägt.

                          Ein möglicher Aspekt, warum bei Dir die soco.discovery Funktion keine UID liefert, müsstest Du nicht hier sondern direkt bei SoCo adressieren. Das Thema supporten wir hier nicht.

                          Viele Grüße
                          Alex

                          Kommentar


                            #14
                            Hallo Alex

                            a2) Und genau bei der Überprüfung ob die UID bestimmt werden konnte hängt sich das plugin bzw soco auf (habe ich mit zusätzlichen debug messages direkt davor, danach und natürlich bei jedem möglichen outcome verifiziert). Er bleibt bei genau dieser line
                            Code:
                            if zone.uid is None:
                            beim ersten speaker der offline ist hängen.
                            a3) sollte er, tut er aber nicht. Er bleibt ganz sicher bei dem If hängen, das mag aber wie du schon schreibst ein Problem von SoCo sein und nur auftreten wenn soco nicht im selben Netz ist und er desshalb von der firewall ein "no route to host" bekommt.

                            Die Soco discovery funktion kann für einen offline speaker keine uid liefern, woher soll er die uid denn nehmen? Das ist ja nichts was er erfindet, die kommt ja vom speaker und wenn der ned läuft kann er sie ned zurück geben.

                            Ich muss das Thema Zeitbedingt jetzt ruhen lassen. Wenn es für andere funktioniert lass den code so wie er ist. Ist wie gesagt OK für mich.
                            Ich hoffe jemand anderer der das Problem auch hat/haben wird findet diesen Thread und kann sich dadurch etwas debug Arbeit ersparen.

                            Kommentar


                              #15
                              Hallo drbone

                              ich sehe nicht, dass der Aufruf zone.uid einen Blocking Call beinhaltet. Das bedeutet, es gibt keinen Grund, dass der Code hier "hängen" bleibt. Ohne Log File und die von Dir ergänzten logger kann und werde ich das Thema nicht weiterverfolgen.

                              Noch eine kleine Hintergrundinfo:
                              Die discovery Funktion kann sehr wohl für einen Offline Speaker eine UID liefern. Die Funktion konsultiert nämlich auch andere Speaker und fragt dort alle "bekannten" Speaker ab. Dazu gehören also auch Speaker, die inder der Vergangenheit mal online waren, anderen Speakern damit bekannt sind und trotzdem zum Zeitpunkt des Ausführens der discovery Funktion gerade offline sind.
                              Gruß
                              Alex

                              Kommentar

                              Lädt...
                              X