Ankündigung

Einklappen
Keine Ankündigung bisher.

CodeReview/Hilfe für Plugin

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

    CodeReview/Hilfe für Plugin

    Hallo,

    ich arbeite gerade an einem Chromecast Plugin und ich benötige Hilfe. Folgendes habe ich bisher implementiert:
    • Play/Pause (Schreiben/Lesen)
    • URL Abspielen
    Leider funktioniert das Lesen des Status momentan nur über polling (scheduler). Das reicht für meinen Einsatzzweck. Aber ich bin ehrgeizig.

    Die Items wären dafür:
    Code:
    [status]
    cc_id="Schlafzimmer"
    cc_cmd="player_state"
    [play_url]
    cc_id="Schlafzimmer"
    cc_playurl="http://meine_datei video/mp4"
    Unten findet sich das Plugin, soweit ich bisher bin. Ich würde mich über Kommentare freuen.
    Folgende Fragen habe ich:
    1. Die Funktion parse_item brauche ich eigentlich nicht, oder? Hier könnte ich Parameter für das Plugin aus den Items Einlesen (aber nur beim Initialisieren)
    2. Wo ist has_iattr /get_iattr dokumentiert?
    3. Ich kann in update_item nicht auf ein parent das Items zugreifen, oder? dann könnte ich die cc_id einfach nur beim Parent Item angeben.
    4. (wie) kann man ein Plugin starten/laufen lassen, ohne sh.py (neu) zu starten (siehe die __main__)
    5. Weitere Tipps/Hinweise?

    Gruß,
    Hendrik

    Hier mein code:
    Code:
    #!/usr/bin/env python3
    from __future__ import print_function
    import logging
    from lib.model.smartplugin import SmartPlugin
    import time
    import pychromecast
    
    
    class chromecast(SmartPlugin):
        PLUGIN_VERSION = "1.1.2"
        ALLOW_MULTIINSTANCE = True
    
        def find_cc(self, devicename):
            chromecasts = pychromecast.get_chromecasts()
            [cc.device.friendly_name for cc in chromecasts]
            self.logger.info("[Chromecast] Searching for Devicename: " + self.devicename)
            self.logger.info("[Chromecast] found: "+ str(chromecasts))
            cast = next(cc for cc in chromecasts if cc.device.friendly_name == self.devicename)
            return cast
    
        def __init__(self, sh, cycle=30, devicename="Schlafzimmer"):
            self._sh        = sh
            self._cycle     = cycle
            self.devicename = devicename 
            self.logger     = logging.getLogger(__name__)
            self.logger.info("Starting Chromecast Instance for Devicename: " + self.devicename)
            print("###"+self.get_instance_name())
            self.cast       = self.find_cc(self.devicename)
            self.cast.wait()
            #sef.cast.device would now return DeviceStatus(friendly_name='Kinderbad', model_name='Chromecast Audio', manufacturer='Google Inc.', api_version=(1, 0), uuid=UUID('e9487631-2c48-cd63-5932-795bdb85f42a'), cast_type='audio')
            #other parameters: cast.status, cast.media_controller
    
        def run(self):
            self.alive = True
            self._sh.scheduler.add(__name__, self._read_play_status, prio=5, cycle=self._cycle, offset=2)  #actually we want to be notified. We need a callback or similar. 
    
        def stop(self):
            self.alive = False
            self._sh.scheduler.remove(__name__)
    
        def parse_item(self, item):  #This is run once at start for each item. Here we could read in parameters
            if 'cc_id' in item.conf:
                if item.conf['cc_id']==self.devicename:
                    if 'cc_cmd' in item.conf:
                        value = item.conf['cc_cmd']
                        if value == 'player_state': 
                            self.playerstate=item                
                        return self.update_item
            else:
                return None
    
        def parse_logic(self, logic):
            pass
    
        def update_item(self, item, caller=None, source=None, dest=None):  
            #called each time an item changes
            self.logger.info('[Chromecast] Update Item triggered. Caller: {0} , Source: {1} , Dest:  {2}'.format(caller,source,dest))       
            if not (caller == "logic:" and source== None) :  # If triggered by logic, we caused this update ourselves (_read_play_status)
                if self.has_iattr(item.conf, 'cc_cmd'):  #does this check if the changed item has the parameter 'cc_player_state'?
                    if self.get_iattr_value(item.conf, 'cc_cmd')=='player_state':
                        mc=self.cast.media_controller
                        if item(): 
                            mc.play()
                            self.logger.info('[Chromecast] Setze Play status auf Play')
                        else: 
                            mc.pause()
                            self.logger.info('[Chromecast] Setze Play status auf Pause')
                if self.has_iattr(item.conf, 'cc_play_url'):
                    val  = self.get_iattr(item.conf, 'cc_play_url')
                    url  = val.split()[0]
                    type = val.split()[1]  
                    mc=self.cast.media_controller        
                    mc.play_media(url, type)
    
    
    
        def _read_play_status(self):
                mc=self.cast.media_controller
                res= (mc.status.player_state=="PLAYING")
                self.logger.info('[Chromecast] Lese Play Status. Ergebnis ' + str(res))
                self.playerstate(res)
    
    
    
    
    if __name__ == '__main__':
        myplugin = chromecast('smarthome-dummy')
        logging.basicConfig(level=logging.DEBUG, format='%(relativeCreated)6d %(threadName)s %(message)s')
        #myplugin.dothis
    Zuletzt geändert von henfri; 09.02.2017, 10:09.

    #2
    Wo ist has_iattr /get_iattr dokumentiert?
    https://github.com/smarthomeNG/smart...ki/SmartPlugin

    Kommentar


      #3
      Hallo,

      vielen Dank.
      Das Plugin läuft jetzt. Ich aktualisiere den Code oben gleich. Ich würde mich freuen, wenn jemand noch über meinen Code schaut und/oder auf die anderen Fragen eingeht.

      Eine neue Frage
      6) wie verhindere ich eine Ausführung von 'update_item' wenn das Plugin selbst ein item aktualisiert?
      Macht das Sinn?
      Code:
      #update_item
              if not (caller == "Logic" and source== None) :  # If triggered by logic, we caused this update ourselves (_read_play_status)
      Gruß,
      Hendrik
      Zuletzt geändert von henfri; 09.02.2017, 10:12.

      Kommentar


        #4
        Hallo,

        das hier habe ich im squeezebox-Plugin gefunden zu meiner Frage 3

        Und zu Frage 6:
        Auch im LMS Plugin wird ein Item so aktualisiert:
        Code:
        item(data[-1], 'LMS', self.address)
        und dann wird so geprüft:
        Code:
        if caller != 'LMS':
        Die Syntax von item() mit mehreren Parametern kannte ich nicht. Wo ist sie dokumentiert?

        Neue Frage 7:
        Ich nutze ja momentan den scheduler und gucke alle x sekunden nach, ob sich der Status geändert hat.
        Das macht das squeezebox-Plugin nicht. Es hält eine Verbindung offen und das Plugin wird getriggert (found_terminator()) wenn Daten ankommen. Ich kann das nicht direkt übertragen, daher frage ich jetzt nicht, wie das genau geht (ich nehme an, dass es damit zu tun hat, dass das Plugin von lib.connection.Client erbt). Aber wie kann ich das Polling vermeiden?
        pychromecast bietet die nötige Infrastruktur:
        Code:
                def new_media_status(self, status):
                        self.sendDeviceStatus()
        
        ...
                def __init__(self, device):
                    self.device.media_controller.register_status_listener(self)
                    self.device.register_status_listener(self)  # Register for receiver status updates.
                    self.device.register_connection_listener(self)
        Edit:

        So funktioniert es:
        Code:
          def __init__(self, sh, cycle=30, devicename="Schlafzimmer"):
              ......
                self.cast.socket_client.receiver_controller.register_status_listener(self)
                self.cast.media_controller.register_status_listener(self)
        
            def new_cast_status(self, status):
                self.cast_status = True
                self.logger.info("[Chromecast] New Cast status for " + self.devicename)
        
            def new_media_status(self,status):
                self.media_status = True
                self.logger.info("[Chromecast] New Media status for " + self.devicename)
        Gruß,
        Hendrik
        Zuletzt geändert von henfri; 09.02.2017, 14:56.

        Kommentar


          #5
          Zum Thema Polling. Als Beispiel kannst du auch in meinem Sonos-Plugin schauen. Da habe ich einen UDP-Eventlistener integriert.

          Gruss,

          Stefan
          Sonos

          Kommentar


            #6
            Hallo,

            so ganz klappt es noch nicht mit dem "Zuhören" statt Pollen.
            Ich bekomme zwar die Meldung
            Code:
            [Chromecast] New Cast status for Mydevice
            Aber der Code danach (auch wenn es einfach nur ein logger.debug ist) wird nicht mehr ausgeführt. Im Log gibt es auch keinen Fehler.

            Da bin ich jetzt ratlos.

            Leider fehlt mir auch eine Dokumentation in pychromecast, was genau bei
            Code:
            self.cast.media_controller.register_status_listener(self)
            übergeben werden muss.

            Mit 'self' übergebe ich ja die ganze Instanz der SmartPlugin Klasse. Keine Ahnung ob das so funktionieren sollte. Aber die Funktion wird ja aufgerufen. Jedoch eben nur die erste Zeile....

            Ich weiß, es ist wohl schwer hier zu helfen, wo pychromecast wohl eher unbekannt ist.
            Hier habe ich ein Beispiel gefunden und hier findet sich die Funktion im Quellcode von pychromecast.

            Vielleicht mag ja jemand mal einen Blick drauf werfen. Ich meine, ich wäre kurz vorm Ziel.

            Gruß,
            Hendrik



            Kommentar


              #7
              Hallo,

              also:
              Code:
                  def new_media_status(self, status):
                      #self.media_status = True
                      self.logger.info("[Chromecast] New. Media status for " + self.devicename)
                      self.logger.info("[Chromecast] New. Media status is  " + self.status)
                      self._read_play_status(self)
              So funktioniert es nicht.
              So funkgioniert es:
              Code:
                  def new_media_status(self, status):
                      #self.media_status = True
                      self.logger.info("[Chromecast] New. Media status for " + self.devicename)
                      self.logger.info("[Chromecast] New. Media status is  " )
                      self._read_play_status()
              Verstehen tue ich es nicht...

              Gruß,
              Hendrik

              Kommentar


                #8
                Ich würde für das Zusammensetzen von Strings das '+' vermeiden. Das geht schief wenn einer der Werte z.B. ein int ist. Nimm lieber die format()-Syntax, die castet dir die Werte automatisch zu Strings. Außerdem bist du viel variabler.

                Gruss,

                Stefan
                Sonos

                Kommentar


                  #9
                  Noch was grundsätzliches: ich entwickle Plugins erstmal immer Standalone, d.h. Als eigenes lauffähiges Script. Mit einer ordentlichen IDE wie Pycharm kann man dann wunderbar Schritt für Schritt debuggen. Erst zum Schluss überführe ich das ganze ins SmarthomeNG-Korsett.

                  Gruss,

                  Stefan
                  Sonos

                  Kommentar

                  Lädt...
                  X