Пример #1
0
    def __init__(self):

        self.control_point = ControlPointSonos(self.ws_port)
        self.control_point.subscribe("new_device_event", self.on_new_device)
        self.control_point.subscribe("removed_device_event", self.on_del_device)
        self.control_point.subscribe('device_event_seq', self.on_device_event_seq)
        self.control_point.start()

        # start MSEARCH
        run_async_function(self.control_point.start_search, (600.0, "ssdp:all"), 0.001)
Пример #2
0
class ControlPointScrob(object):

    ###########################################################################
    # class vars
    ###########################################################################

    current_renderer_events_avt = {}

    now_playing = ''
    now_extras = ''
    now_playing_dict = {}
    now_extras_dict = {}

    known_zone_players = {}
    known_zone_names = {}
    known_media_servers = {}
    known_media_renderers = {}

    zoneattributes = {}
    musicservices = {}
    mediaservers = {}
    databases = {}
    
    subscriptions = []
    subscription_ids = {}
    at_lookup = {}
    at_subscription_ids = {}
    at_service =  {}
    cd_subscription_ids = {}
    cd_service =  {}
    zt_lookup = {}
    zt_subscription_ids = {}
    zt_service =  {}

    event_queue = []

    zone_groups = {}
    zone_group_coordinators_lookup = {}

    current_track_scrobbled = {}
    current_play_state = {}
    current_position_info = {}
    current_track_duration = {}
    current_track_relative_time_position = {}
    current_track_absolute_time_position = {}
    current_track_URI = {}
    current_track_start = {}
    current_track_metadata = {}
    current_transport_metadata = {}
    
    avt_track_URI = {}

    previous_play_state = {}
    previous_track_URI = {}
    
    zone_grouped = {}

    transport_error = {}

    ###########################################################################
    # command line parser
    ###########################################################################

    usage = "usage: %prog [options] arg"
    parser = OptionParser(usage)
    
    parser.add_option("-m", "--module", action="append", type="string", dest="modcheckmods")
    parser.add_option("-d", "--debug", action="store_true", dest="debug")
    parser.add_option("-v", "--verbose",
                      action="store_true", dest="verbose", default=False,
                      help="print verbose status messages to stdout")
    parser.add_option("-l", "--log",
                      action="store_true", dest="logging", default=False,
                      help="print log messages to stdout")

    (options, args) = parser.parse_args()

    if options.debug:
        modcheck['all'] = True
    if options.modcheckmods:
        for m in options.modcheckmods:
            modcheck[m] = True

    __enable_webserver_logging__ = True
    __enable_events_logging__ = True

    ###########################################################################
    # ini parser
    ###########################################################################

    config = ConfigParser.ConfigParser()
    config.optionxform = str
    config.read('pycpoint.ini')

    # get ports to use
    ws_port = 50103
    try:        
        ws_port = int(config.get('INI', 'playcounts_port'))
    except ConfigParser.NoOptionError:
        pass

    # get playcounts file
    pc_file = 'playcounts.log'
    try:        
        pc_file = int(config.get('INI', 'playcounts_file'))
    except ConfigParser.NoOptionError:
        pass

    # get log file
    log_file = 'playcountslog.log'
    try:        
        log_file = int(config.get('INI', 'playcounts_log_file'))
    except ConfigParser.NoOptionError:
        pass

    # get log file 2
    log_file2 = 'playcountslog2.log'
    try:        
        log_file2 = int(config.get('INI', 'playcounts_log2_file'))
    except ConfigParser.NoOptionError:
        pass

    ###########################################################################
    # __init__
    ###########################################################################

    def __init__(self):

        self.control_point = ControlPointSonos(self.ws_port)
        self.control_point.subscribe("new_device_event", self.on_new_device)
        self.control_point.subscribe("removed_device_event", self.on_del_device)
        self.control_point.subscribe('device_event_seq', self.on_device_event_seq)
        self.control_point.start()

        # start MSEARCH
        run_async_function(self.control_point.start_search, (600.0, "ssdp:all"), 0.001)

    def subscribe_to_device(self, service, udn, servicetype, name):
        try:
            service.event_subscribe(self.control_point.event_host, self._event_subscribe_callback, (udn, servicetype, service, name), True, self._event_renewal_callback)
            self.subscriptions.append((service, udn, servicetype, name))
        except:
            raise Exception("Error occured during subscribe to device")

    def subscribe_for_variable(self, device, service, variable):
        try:
            print variable
            print service.get_state_variable(variable)
            service.subscribe_for_variable(variable, self._event_variable_callback)
        except:
            raise Exception("Error occured during subscribe for variable")

    def _event_variable_callback(self, name, value):
        print "Event message!"
        print 'State variable:', name
        print 'Variable value:', value

#    def renew_device_subscription(self, device, service):
#        try:
#            device.services[service].event_renew(self.control_point.event_host, self._event_renewal_callback, None)
#        except:
#            raise Exception("Error occured during device subscription renewal")

    def _event_subscribe_callback(self, cargo, subscription_id, timeout):
        log.debug('Event subscribe done cargo=%s sid=%s timeout=%s', cargo, subscription_id, timeout)
        udn, servicetype, service, name = cargo
        uuid = udn[5:]
        if servicetype == 'AVTransport':
            self.at_lookup[uuid] = subscription_id
            self.at_subscription_ids[subscription_id] = udn
            self.at_service[subscription_id] = service
            self.current_track_scrobbled[subscription_id] = False
            self.current_play_state[subscription_id] = None
            self.current_position_info[subscription_id] = None
            self.current_track_duration[subscription_id] = None
            self.current_track_relative_time_position[subscription_id] = None
            self.current_track_absolute_time_position[subscription_id] = None
            self.current_track_URI[subscription_id] = None
            self.current_track_start[subscription_id] = None
            self.previous_track_URI[subscription_id] = None
            self.previous_play_state[subscription_id] = None
            self.zone_grouped[subscription_id] = False
            self.current_track_metadata[subscription_id] = None
            self.current_transport_metadata[subscription_id] = None
            self.avt_track_URI[subscription_id] = None
            self.transport_error[subscription_id] = False
            self.zone_groups[subscription_id] = None
            self.zone_group_coordinators_lookup[subscription_id] = None
        if servicetype == 'ContentDirectory':
            self.cd_subscription_ids[subscription_id] = udn
            self.cd_service[subscription_id] = service
        if servicetype == 'ZoneGroupTopology':
            self.zt_lookup[uuid] = subscription_id
            self.zt_subscription_ids[subscription_id] = udn
            self.zt_service[subscription_id] = service
        self.subscription_ids[subscription_id] = '%s, %s' % (servicetype, name)
        self.process_event_queue(subscription_id)

    def _event_renewal_callback(self, cargo, subscription_id, timeout):
# TODO: add error processing for if renewal fails - basically resubscribe. NEW - check if this is catered for in 0.10.0
        log.debug('Event renew done cargo=%s sid=%s timeout=%s', cargo, subscription_id, timeout)

    def _event_unsubscribe_callback(self, cargo, subscription_id):
        if self.options.verbose:
            out = "cancelled subscription for service: %s\n" % str(cargo)
            self.write_log(out)
        log.debug('Event unsubscribe done cargo=%s sid=%s', cargo, subscription_id)
        
    def unsubscribe_from_device(self, serviceset):
        service, udn, servicetype, name = serviceset
        try:
            service.event_unsubscribe(self.control_point.event_host, self._event_unsubscribe_callback, serviceset)
        except:
            raise Exception("Error occured during unsubscribe from device")

    def cancel_subscriptions(self):
        log.debug("Cancelling subscriptions")
        for serviceset in self.subscriptions:
            log.debug("Service: %s", serviceset)
            self.unsubscribe_from_device(serviceset)

    def get_zone_details(self, device):
        return self.control_point.get_zone_attributes(device)

#    def _renew_subscriptions(self):
#        """ Renew subscriptions
#        """
#        self.renew_device_subscription(self.control_point.current_renderer, self.control_point.avt_s)
#        self.renew_device_subscription(self.control_point.current_renderer, self.control_point.rc_s)

    def on_new_device(self, device_object):
        log.debug('got new device: %s' % str(device_object))
        log.debug('fn: %s' % str(device_object.friendly_name))
        log.debug('loc: %s' % str(device_object.location))
        log.debug('add: %s' % str(device_object.address))
#        print ">>>> new device: " + str(device_object.friendly_name) + " at " + str(device_object.address) + "  udn: " + str(device_object.udn)

        device_list = []
        if device_object.devices:
            root_device = device_object
            root_device.devices = []
            device_list.append(root_device)
            device_list.extend(device_object.devices)
        else:
            device_list.append(device_object)

        for device_item in device_list:
            log.debug('new device: %s' % str(device_item))
            log.debug('new device type: %s' % str(device_item.device_type))
            log.debug('new device udn: %s' % str(device_item.udn))                                    
            log.debug('new device services: %s' % str(device_item.services))
            # assumes root device is processed first so that zone name is known
            newmediaserver = False
            newmediarenderer = False
            t = device_item.device_type
            if 'ZonePlayer' in t:
                self.on_new_zone_player(device_item)
                # now register zoneplayer as server and renderer
#                newmediaserver = self.on_new_media_server(device_item)
                newmediarenderer = self.on_new_media_renderer(device_item)
            log.debug('new device fn: %s' % str(device_item.friendly_name))                                    

    def on_new_zone_player(self, device_object):
        self.known_zone_players[device_object.udn] = device_object
        self.zoneattributes[device_object.udn] = self.get_zone_details(device_object)
        self.musicservices[device_object.udn] = self.get_music_services(device_object)
        log.debug('new zone player - %s' % self.zoneattributes[device_object.udn]['CurrentZoneName'])
        self.known_zone_names[device_object.udn] = self.zoneattributes[device_object.udn]['CurrentZoneName']
        self.subscribe_to_device(self.control_point.get_zt_service(device_object), device_object.udn, "ZoneGroupTopology", self.known_zone_names[device_object.udn])
        self.subscribe_to_device(self.control_point.get_ms_service(device_object), device_object.udn, "MusicServices", self.known_zone_names[device_object.udn])
        self.subscribe_to_device(self.control_point.get_dp_service(device_object), device_object.udn, "DeviceProperties", self.known_zone_names[device_object.udn])
        self.subscribe_to_device(self.control_point.get_sp_service(device_object), device_object.udn, "SystemProperties", self.known_zone_names[device_object.udn])
        self.subscribe_to_device(self.control_point.get_gm_service(device_object), device_object.udn, "GroupManagement", self.known_zone_names[device_object.udn])

    def on_new_media_server(self, device_object):
        if device_object.udn in self.known_media_servers:
#            print '>>>> new server device: duplicate'
            return False
        self.known_media_servers[device_object.udn] = device_object
        # subscribe to events from this device
        self.subscribe_to_device(self.control_point.get_cd_service(device_object), device_object.udn, "ContentDirectory", self.known_zone_names[device_object.udn])
        self.subscribe_to_device(self.control_point.get_ms_cm_service(device_object), device_object.udn, "ServerConnectionManager", self.known_zone_names[device_object.udn])
        return True

    def on_new_media_renderer(self, device_object):
        if device_object.udn in self.known_media_renderers:
#            print '>>>> new renderer device: duplicate'
            return False
        self.known_media_renderers[device_object.udn] = device_object
        # subscribe to events from this device
        self.subscribe_to_device(self.control_point.get_at_service(device_object), device_object.udn, "AVTransport", self.known_zone_names[device_object.udn])
        self.subscribe_to_device(self.control_point.get_rc_service(device_object), device_object.udn, "RendereringControl", self.known_zone_names[device_object.udn])
        self.subscribe_to_device(self.control_point.get_mr_cm_service(device_object), device_object.udn, "RendererConnectionManager", self.known_zone_names[device_object.udn])
        return True

    def on_del_device(self, udn):
        # TODO: unsubscribe from events from deleted device
        if udn in self.known_media_servers:
            del self.known_media_servers[udn]
        if udn in self.known_media_renderers:
            del self.known_media_renderers[udn]
        # do this last so name above can be generated correctly
        # TODO: save name from initial generation
        if udn in self.known_zone_players:
            del self.known_zone_players[udn]
    



    def check_playing(self, sid):
    
        if self.options.verbose:
            out  = "check_playing\n"
            out += "    sid: %s\n" % sid
            out += "    subscription_ids              : %s\n" % self.subscription_ids
            out += "    at_lookup                     : %s\n" % self.at_lookup
            out += "    at_subscription_ids           : %s\n" % self.at_subscription_ids
            out += "    cd_subscription_ids           : %s\n" % self.cd_subscription_ids
            out += "    zt_lookup                     : %s\n" % self.zt_lookup
            out += "    zt_subscription_ids           : %s\n" % self.zt_subscription_ids
            out += "    zone_groups                   : %s\n" % self.zone_groups
            out += "    zone_group_coordinators_lookup: %s\n" % self.zone_group_coordinators_lookup
            out += "    zone_grouped                  : %s\n\n" % self.zone_grouped
            self.write_log(out)
          
        # check whether track was moved to another zone
        if self.current_play_state[sid] == 'STOPPED' and self.avt_track_URI[sid] == '':
            # TODO: check whether we need to check other fields for streaming tracks
            # check for a transport stream error
            if self.transport_error[sid]:
                # transport error - check existing track stats
                ZP = self.zoneattributes[self.at_subscription_ids[sid]]['CurrentZoneName']
                delta = self.getmintime(time.time(), self.current_track_absolute_time_position[sid], self.current_track_start[sid])
                if self.options.logging:
                    out = "%s Transport Error. Old duration: %s, position: %s, delta: %s\n" % (ZP, self.current_track_duration[sid], self.current_track_relative_time_position[sid], delta)
                    self.write_log(out)
                self.check_scrobble(sid, self.current_track_duration[sid], self.current_track_relative_time_position[sid], delta)
            # ignore this notification
            return
            
        # check whether track was passed/grouped from another zone
        passed_track = False
        # TODO: check whether z_g conditional supports both cases
        if self.avt_track_URI[sid].startswith('x-rincon:'):
            # this track was passed from another zone
            # get other zone sid
            uuid = self.avt_track_URI[sid][9:]
            other_sid = self.at_lookup[uuid]
            # set scrobbled flag
            self.current_track_scrobbled[sid] = self.current_track_scrobbled[other_sid]
            passed_track = True

        self.zone_grouped[sid] = self.check_zone_grouped(sid)
        if self.zone_grouped[sid] == True:
            # this zone is grouped with another zone
            # get other zone sid
            coord_sid = self.get_zone_coordinator(sid)
            other_sid = self.at_lookup[coord_sid]
            # set scrobbled flag
            self.current_track_scrobbled[sid] = self.current_track_scrobbled[other_sid]
            passed_track = True

        if self.options.logging:
            ZP = self.zoneattributes[self.at_subscription_ids[sid]]['CurrentZoneName']
            out = "%s play_state: %s\n" % (ZP, self.current_play_state[sid])
            self.write_log(out)
        # get latest position info
        self.current_position_info[sid] = self.get_position_info(sid)
        if self.options.logging:
            out = "%s current_position_info: %s\n" % (ZP, self.current_position_info[sid])
            self.write_log(out)
        # check if track has changed
        if self.current_track_URI[sid] == self.current_position_info[sid]['TrackURI'] or passed_track:
            # same track
            # whatever the play state, set/update position and check scrobble
            self.current_track_duration[sid] = self.current_position_info[sid]['TrackDuration']
            self.current_track_relative_time_position[sid] = self.current_position_info[sid]['RelTime']
            if self.options.logging:
                out = "%s Same track. Duration: %s, position: %s\n" % (ZP, self.current_track_duration[sid], self.current_track_relative_time_position[sid])
                self.write_log(out)
            self.current_track_absolute_time_position[sid] = time.time()
            self.current_track_metadata[sid] = self.current_position_info[sid]['TrackMetaData']
            self.check_scrobble(sid, self.current_track_duration[sid], self.current_track_relative_time_position[sid])
            if passed_track:
                # set track details to details from other zone
                self.current_track_URI[sid] = self.current_track_URI[other_sid]
                self.current_track_start[sid] = self.current_track_start[other_sid]
                self.current_track_metadata[sid] = self.current_track_metadata[other_sid]
                self.previous_track_URI[sid] = self.previous_track_URI[other_sid]
        else:
            # new track - check how long previous track played for (only if it was playing, otherwise it has already been processed)
            if self.previous_play_state[sid] == 'PLAYING':
                delta = self.getmintime(time.time(), self.current_track_absolute_time_position[sid], self.current_track_start[sid])
                if self.options.logging:
                    out = "%s New track. Old duration: %s, position: %s, delta: %s\n" % (ZP, self.current_track_duration[sid], self.current_track_relative_time_position[sid], delta)
                    self.write_log(out)
                self.check_scrobble(sid, self.current_track_duration[sid], self.current_track_relative_time_position[sid], delta)
            # set up for new track
            self.current_track_start[sid] = time.time()
            self.current_track_URI[sid] = self.current_position_info[sid]['TrackURI']
            self.current_track_duration[sid] = self.current_position_info[sid]['TrackDuration']
            self.current_track_relative_time_position[sid] = self.current_position_info[sid]['RelTime']
            if self.options.logging:
                out = "%s New track. Duration: %s, position: %s\n" % (ZP, self.current_track_duration[sid], self.current_track_relative_time_position[sid])
                self.write_log(out)
            self.current_track_absolute_time_position[sid] = time.time()
            self.current_track_metadata[sid] = self.current_position_info[sid]['TrackMetaData']
            self.current_track_scrobbled[sid] = False
            self.check_scrobble(sid, self.current_track_duration[sid], self.current_track_relative_time_position[sid])
        self.previous_play_state[sid] = self.current_play_state[sid]

    def get_position_info(self, sid):
        atservice = self.at_service[sid]
        return atservice.GetPositionInfo(InstanceID=0)

    def getmintime(self, now, t1, t2):
        m = 0
        if t1 != None:
            d1 = now - t1
            m = d1
        if t2 != None:
            d2 = now - t2
            m = d2
        if t1 != None and t2 != None:
            m = min(d1, d2)
        return m

    def check_scrobble(self, sid, duration, position, delta=0):
        if not self.current_track_scrobbled[sid]:
            # check whether we have valid data
            if duration == 'NOT_IMPLEMENTED' or position == 'NOT_IMPLEMENTED':
                return
            # delta = playing time since position was logged
            totalsecs = self.makeseconds(duration)
            if totalsecs == 0:
                # assume this is a radio station
                self.current_track_scrobbled[sid] = True
                self.previous_track_URI[sid] = self.current_track_URI[sid]
                return
            currsecs = self.makeseconds(position) + delta
            percentage = (100 * currsecs)/totalsecs
            if percentage >= 50.0:
                self.scrobble(sid)

    def scrobble(self, sid):
        ZP = self.zoneattributes[self.at_subscription_ids[sid]]['CurrentZoneName']
        trackURI = self.current_track_URI[sid]
        database = ''
        extras = ''
        if '?sid=' in trackURI and '&flags=' in trackURI:
            musicservices = self.musicservices[self.at_subscription_ids[sid]]
            id = trackURI.split('?sid=')[1].split('&flags=')[0]
            service = musicservices[id]
        elif trackURI.startswith('x-file-cifs:'):
            service = 'Sonos Library'
        elif trackURI.startswith('http://') and '.x-udn/' in trackURI:
            mediaservers = self.mediaservers[self.at_subscription_ids[sid]]
            databases = self.databases[self.at_subscription_ids[sid]]
            uuid = trackURI[7:].split('.x-udn/')[0]
            service = mediaservers[uuid]
            database = databases[uuid]
            filename = trackURI.split(os.sep)[-1]
            if filename.startswith(database):
                extras = ',"%s","%s"' % (filename, database)
        elif trackURI.startswith('lfmtrack:http'):
            service = 'last.fm'
            if self.current_transport_metadata[sid] != "":
                extras = ',%s' % self.unwrap_transport_metadata(self.current_transport_metadata[sid])
        else:
            service = "UNKNOWN"

        trackdata = self.unwrap_metadata(self.current_track_metadata[sid])
#        currenttime = time.time()

        scrob_log ='"%s","%s","%s",%s,"%s","%s"%s\n' % (self.current_track_start[sid], ZP, service, trackdata, self.current_track_duration[sid], trackURI, extras)
        scrob_log = scrob_log.encode(enc, 'replace')
        print scrob_log
        self.write_log(scrob_log)
        
        f = open(self.pc_file, 'a')
        f.write(scrob_log)
        f.close()
        self.current_track_scrobbled[sid] = True
        self.previous_track_URI[sid] = self.current_track_URI[sid]

    def write_log(self, out):
        f = open(self.log_file, 'a')
        f.write(out)
        f.close()

    def write_log2(self, out):
        f = open(self.log_file2, 'a')
        f.write(out)
        f.close()

    def unwrap_metadata(self, metadata):
        title = artist = album = ''
        elt = self.from_string(metadata)
        ns = "{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}"
        self.remove_namespace(elt, ns)
        item = elt.find('item')
        if item != None:
            ititle = item.find('{http://purl.org/dc/elements/1.1/}title')
            if ititle != None:
                title = ititle.text
            iartist = item.find('{http://purl.org/dc/elements/1.1/}creator')
            if iartist != None:
                artist = iartist.text
            ialbum = item.find('{urn:schemas-upnp-org:metadata-1-0/upnp/}album')
            if ialbum != None:
                album = ialbum.text
        details = '"%s","%s","%s"' % (title, artist, album)
        return details

    def unwrap_transport_metadata(self, metadata):
        title = ''
        elt = self.from_string(metadata)
        ns = "{urn:schemas-upnp-org:metadata-1-0/DIDL-Lite/}"
        self.remove_namespace(elt, ns)
        item = elt.find('item')
        if item != None:
            ititle = item.find('{http://purl.org/dc/elements/1.1/}title')
            if ititle != None:
                title = ititle.text
        details = '"%s"' % title
        return details

    def get_music_services(self, device):
        service = self.control_point.get_ms_service(device)
        service_response = service.ListAvailableServices()
        musicservices = {}
        if 'AvailableServiceDescriptorList' in service_response:
            elt = AvailableServices().from_string(service_response['AvailableServiceDescriptorList'])
            service_response['AvailableServiceDescriptorList'] = elt.get_items()
            for item in service_response['AvailableServiceDescriptorList']:
                musicservices[item.Id] = item.Name
        return musicservices    

    def makeseconds(self, time):
        if not ':' in time:
            return 0
        h, m, s = time.split(':')
        return (int(h)*60*60 + int(m)*60 +int(float(s)))

    def process_event(self, event, sid):
        esid, seq, changed_vars = event
        if sid == esid:
            # subscription callback has been called - check for events and process and dequeue
            if self.options.verbose:
                out = "notification dequeued: seq=%s, sid=%s\nchanged_vars=%s\n\n" % (seq, sid, changed_vars)
                self.write_log(out)
            self.on_device_event_seq(sid, seq, changed_vars)
            return None
        return event

    def process_event_queue(self, sid):
        # process any outstanding events for this sid
        self.event_queue = [event for event in self.event_queue if self.process_event(event, sid)]
        time.sleep(0.1)     # just in case - replace with locks on queue
        self.event_queue = [event for event in self.event_queue if self.process_event(event, sid)]
            
    def on_device_event_seq(self, sid, seq, changed_vars):

        if not sid in self.subscription_ids:
            # notification arrived before subscription callback - queue event
            if self.options.verbose:
                out = "notification queued: seq=%s, sid=%s\nchanged_vars=%s\n\n" % (seq, sid, changed_vars)
                self.write_log(out)
            self.event_queue.append((sid, seq, changed_vars))
            return

        if self.options.verbose:
            out = "service, Zone=%s, seq=%s, sid=%s\nchanged_vars=%s\n\n" % (self.subscription_ids[sid], seq, sid, changed_vars)
            self.write_log(out)
        if self.subscription_ids[sid].startswith('ZoneGroupTopology'):
            self.process_zgt(sid, self.subscription_ids[sid][19:], changed_vars)
    
        # check it is a LastChange event
        if 'LastChange' in changed_vars and changed_vars['LastChange'] != None and changed_vars['LastChange'] != 'NOT_IMPLEMENTED'  and changed_vars['LastChange'] != '0':

            if sid in self.at_subscription_ids:

                ZP = self.zoneattributes[self.at_subscription_ids[sid]]['CurrentZoneName']
                
                # event from AVTransport
                # TODO: check if we need to remove the ns, and if it is actually removed anyway
                ns = "{urn:schemas-upnp-org:metadata-1-0/AVT/}"
                elt = self.from_string(changed_vars['LastChange'])
                self.remove_namespace(elt, ns)
                # check if it is initial event message
                if self.current_renderer_events_avt == {}:
                    # save all tags
                    self.process_event_tags_avt(elt, self.current_renderer_events_avt)
                else:
                    # not initial message, update vars
                    tag_list = {}
                    self.process_event_tags_avt(elt, tag_list)
                    # save changed tags                    
                    for key, value in tag_list.iteritems():
                        self.current_renderer_events_avt[key] = value

                self.current_play_state[sid] = self.current_renderer_events_avt['TransportState']
                self.avt_track_URI[sid] = self.current_renderer_events_avt['CurrentTrackURI']
                if self.current_play_state[sid] != 'STOPPED':
                    self.current_transport_metadata[sid] = self.current_renderer_events_avt['{urn:schemas-rinconnetworks-com:metadata-1-0/}EnqueuedTransportURIMetaData']
                if 'TransportErrorDescription' in self.current_renderer_events_avt:
                    self.transport_error[sid] = True
                else:
                    self.transport_error[sid] = False
                self.check_playing(sid)

        elif 'ThirdPartyMediaServers' in changed_vars:

            if sid in self.zt_subscription_ids:

                uuid = sid.split('_sub')[0]
                self.mediaservers[uuid], self.databases[uuid] = self.process_thirdpartymediaservers(changed_vars['ThirdPartyMediaServers'])
 
    def process_event_tags_avt(self, elt, event_list):
        # save values
        InstanceID = elt.find('InstanceID')
        if InstanceID != None:
            event_list['InstanceID'] = InstanceID.get('val')    # not checking this at present, assuming zero
            for child in elt.findall('InstanceID/*'):
                nodename = child.tag
                val = child.get('val')
                event_list[nodename] = val

    def process_thirdpartymediaservers(self, thirdpartymediaservers):
        elt = self.from_string(thirdpartymediaservers)
        mediaservers = {}
        databases = {}
        ms = elt.findall('MediaServer')
        for entry in ms:
            udn = entry.attrib['UDN']
            mediaservers[udn] = entry.attrib['Name']
            location = entry.attrib['Location']
            dbname = ''
            xml = urllib.urlopen(location).read()
            elt = parse_xml(xml)
            if elt != None:
                root = elt.getroot()
                ns = "{urn:schemas-upnp-org:device-1-0}"
                self.remove_namespace(root, ns)
                if root != None:
                    dev = root.find('device')
                    if dev != None:
                        serialNumber = dev.find('serialNumber')
                        if serialNumber != None:
                            dbname = serialNumber.text
            databases[udn] = dbname
        return (mediaservers, databases)

    def from_string(self, aString):
        elt = parse_xml(aString)
        elt = elt.getroot()
        return elt

    def remove_namespace(self, doc, ns):
        """Remove namespace in the passed document in place."""
        nsl = len(ns)
        for elem in doc.getiterator():
            if elem.tag.startswith(ns):
                elem.tag = elem.tag[nsl:]

    def process_zgt(self, sid, node, nodedict):
        if 'ZoneGroupState' in nodedict:
            xml = nodedict['ZoneGroupState']
            elt = parse_xml(xml)
            elt = elt.getroot()
            out = node + ' ZoneGroupTopology\n'
            self.zone_groups[sid] = {}
            self.zone_group_coordinators_lookup[sid] = {}
            for e in elt.findall('ZoneGroup'):
                zg_tag = e.tag
                zg_coord = e.get('Coordinator')
                zone_group_members = []            
                for child in e.findall('ZoneGroupMember'):
                    zgm_tag = child.tag
                    zgm_uuid = child.get('UUID')
                    zgm_zonename = child.get('ZoneName')
                    zone_group_members.append((zgm_uuid, zgm_zonename))
                    if zgm_uuid == zg_coord:
                        zg_coord_name = zgm_zonename
                out += '    ' + 'Coordinator ' + zg_coord_name + '\n'
                zgm = []
                for m in zone_group_members:
                    uuid, name = m
                    out += '        ' + 'Member ' + name + '\n'
                    zgm.append(uuid)
                    self.zone_group_coordinators_lookup[sid][uuid] = zg_coord
                self.zone_groups[sid][zg_coord] = zgm
            out += '\n'
            if self.options.verbose:
                self.write_log2(out)

    def check_zone_grouped(self, sid):
        # get udn of zone
        zudn = self.at_subscription_ids[sid][5:]
        # get sid of zone ZGT
        zsid = self.zt_lookup[zudn]
        zgsid = sid[5:].split('_sub')[0]
        if not zgsid in self.zone_groups[zsid]:
            # sid does not exist as coordinator
            return True
        else:
            return False

    def get_zone_coordinator(self, sid):
        # get udn of zone
        zudn = self.at_subscription_ids[sid][5:]
        # get sid of zone ZGT
        zsid = self.zt_lookup[zudn]
        zgsid = sid[5:].split('_sub')[0]
        return self.zone_group_coordinators_lookup[zsid][zgsid]

    def _main_quit(self):
        print "cancelling subscriptions, please wait..."
        self.cancel_subscriptions()
        print "subscriptions cancelled."
        reactor.main_quit()
Пример #3
0
import brisa
import brisa.core.reactors
reactor = brisa.core.reactors.install_default_reactor()

from control_point_sonos import ControlPointSonos
from brisa.upnp.control_point.device import Device
from brisa.upnp.control_point.device_builder import DeviceAssembler

from brisa.core.ireactor import EVENT_TYPE_READ

##xml = "http://10.0.0.176:1400/xml/zone_player.xml"
##my_device = Device()
##dev_assembler = DeviceAssembler(my_device, xml)
##dev_assembler.mount_device()
cp = ControlPointSonos()
#cp.start()
#cp.start_search(60)

import socket
from struct import pack

##my_sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM,
##                                         socket.IPPROTO_UDP)
##
##my_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
##my_sock.bind(('', 1900))
##mreq = pack('4sl', socket.inet_aton('239.255.255.250'),
##                             socket.INADDR_ANY)
##my_sock.setsockopt(socket.IPPROTO_IP, socket.IP_ADD_MEMBERSHIP, mreq)