コード例 #1
0
    def is_zone_id(self, zone_id=None, zone_mcid=None, raises=False):
        ''' Returns True if the id corresponds to the current zone.

        Args:
            zone_id (string): zone id
            zone_mcid (string): zone MusicCast id
            raises (boolean): if True, an exception is raised if result is False
        '''
        if zone_id is not None:
            if self.id == zone_id: return True
            msg_id = zone_id
        elif zone_mcid is not None:
            if self.device.is_mcready(raises=False):
                if self.mcid == zone_mcid: return True
                msg_id = zone_mcid
            else:  # request to check against mcid on non MusicCast Ready device
                if raises:
                    raise mcx.LogicError(
                        'Can not determine MusicCast id on non-MusicCast Ready device'
                    )
                else:
                    return False
        else:
            if raises:
                raise mcx.ConfigError(
                    'No valid Zone id argument to check against.')
            else:
                return False
        if raises:
            raise mcx.ConfigError(''.join(
                ('Zone id ', msg_id, ' does not match this zone.')))
        else:
            return False
コード例 #2
0
    def get_feature(self, flist):
        ''' Returns a branch from the getFeatures tree.

        This method retrieves a branch or leaf from a JSON type object.
        The argument is a list made of strings and/or pairs of string.
        For each string, the method expect to find a dictionary as the next branch,
        and selects the value (which is another branch or leaf) returned by that string as key.
        For each pair (key, value), it expects to find an array or similar objects,
        and in that case it searches for the array that contains the right 'value' for that 'key'.

        This method helps in reading the responses from Yamaha MusicCast API.

        Args:
            flist: list representing a leaf or a branch from a JSON type of string

        Raises:
            CommsError, ConfigError.
        '''
        branch = self._features
        if isinstance(flist, basestring): # Python 3: isinstance(arg, str)
            flist = (flist,)
        for arg in flist:
            if isinstance(arg, basestring):
                try: branch = branch[arg]
                except KeyError:
                    raise mcx.ConfigError(''.join(('Argument <', str(arg),
                                                   '> not found in current branch: ',
                                                   str(branch))))
                except TypeError:
                    raise mcx.CommsError(''.join(('The current branch is not a dictionary: ',
                                                  str(branch))))
            else: # assume arg is a pair (key, value)
                try:
                    key = arg[0]
                    value = arg[1]
                except (IndexError, TypeError):
                    raise mcx.CommsError(''.join(('Argument <', str(arg), '> should be a pair.')))
                found = False
                for obj in branch: # assume branch is an array
                    try: found = (obj[key] == value)
                    except KeyError:
                        raise mcx.ConfigError(''.join(('Key <', str(key),
                                                       '> not found in current branch: ',
                                                       str(branch))))
                    except TypeError:
                        raise mcx.CommsError(''.join(('The current branch is not a dictionary: ',
                                                      str(branch))))
                    if found: break
                if not found:
                    raise mcx.ConfigError(''.join(('Value <', str(value),
                                                   '> for key <', str(key),
                                                   '> not found in array.')))
                branch = obj
        return branch
コード例 #3
0
 def get_yxcid(self, raises=False):
     ''' Returns the Yamaha device id, if it exists.'''
     if not self.is_mcready(raises): return None
     try: return self._dev_info['device_id']
     except KeyError:
         if raises: raise mcx.ConfigError('No device_id in getFeatures.')
         else: return None
コード例 #4
0
    def find_infotype(self, play_info_type):
        ''' Retrieves the info_type instance.

        Only used (for now) by the event lambdas.
        '''
        try: return self._mcinfotype_d[play_info_type]
        except KeyError:
            raise mcx.ConfigError(''.join(('InfoType <', play_info_type, '> not found.')))
コード例 #5
0
    def _get_current_input(self, raises=False):
        ''' Returns the current input in this zone or None

        Args:
            raises (boolean): if True, raises an exception if not found
        '''
        if self._input is None and raises:
            raise mcx.ConfigError('Input unassigned.')
        return self._input
コード例 #6
0
    def get_remote_dev(self, raises=False):
        ''' Returns the remote Device object.

        Args:
            raises (boolean): if True, raises an exception if not found
        '''
        if self._remote_dev is None and raises:
            raise mcx.ConfigError('Remote device unassigned')
        return self._remote_dev
コード例 #7
0
    def get_control_zone(self, raises=False):
        ''' Returns the remote Zone object.

        Args:
            raises (boolean): if True, raises an exception if not found
        '''
        if self._remote_zone is None and raises:
            raise mcx.ConfigError('Remote zone unassigned')
        return self._remote_zone
コード例 #8
0
    def get_input(self, input_id=None, input_mcid=None, raises=False):
        ''' Returns the :class:`Input` object from its id or mcid.

        If input_id is present, then it is used, otherwise input_mcid is used.

        Args:
            zone_id (string): the id of the zone searched
            zone_mcid (string): the MusicCast id of the zone searched
            raises (boolean): if True, raises an exception instead of returning ``False``

        Returns:
            :class:`Input` object if found, or ``None``

        Raises:
            ConfigError or LogicError.
        '''
        if input_id is not None:
            try: return self._inputs_d[input_id]
            except KeyError:
                if raises:
                    raise mcx.ConfigError(''.join(('Input <', input_id, '> not found in device <',
                                                   self.id, '>.')))
                else: return None
        elif input_mcid is not None:
            if not self.musiccast:
                if raises:
                    raise mcx.LogicError(''.join(('Can not find MusicCast input <', input_mcid,
                                                  '> on non-MusicCast device <', self.id, '>.')))
                else: return None
            # TODO: make a dictionary to find the MusicCast input out of its MusicCast id.
            for inp in self.inputs:
                if inp.mcid == input_mcid: return inp
            if raises:
                raise mcx.ConfigError(''.join(('MusicCast input <', input_mcid,
                                               '> not found in device <', self.id, '>.')))
            else: return None
        else: # both ids are None
            if raises:
                raise mcx.ConfigError(''.join(('No valid arguments in get_input() on device <',
                                               self.id, '>.')))
            else: return None
コード例 #9
0
    def get_zone(self, zone_id=None, zone_mcid=None, raises=False):
        ''' Returns the :class:`Zone` object from one of its identifications.

        Either identifications can be provided.  The behaviour in case both are provided
        is determined by the method :meth:`is_zone_id` in :class:`Zone`.

        Args:
            zone_id (string): the id of the zone searched
            zone_mcid (string): the MusicCast id of the zone searched
            raises (boolean): if True, raises an exception instead of returning ``False``
        '''
        if zone_id is None and zone_mcid is None:
            if raises: raise mcx.ConfigError('No valid Zone id arguments to get Zone.')
            else: return None
        for zone in self.zones:
            if zone.is_zone_id(zone_id=zone_id, zone_mcid=zone_mcid, raises=False):
                return zone
        if raises:
            raise mcx.ConfigError(''.join(('No Zone found with id ',
                                           zone_id if zone_id else zone_mcid, '.')))
        else:
            return None
コード例 #10
0
    def find_mczone(self, mcid):
        ''' Returns the MusicCast zone from its id.

        Needed by the event processor, it is called by the lambdas.
        This is redundant with get_zone() but probably faster.

        Args:
            mcid (string): MusicCast id of the zone.  Normally one of ``main``,
                ``zone2``, ``zone3``, ``zone4``.  See list ``_ZONES``.

        Raises:
            ConfigError: if the zone is not found, either because it is not a
                valid zone or because it does not exist in this device.
        '''
        try: return self._mczone_d[mcid]
        except KeyError:
            raise mcx.ConfigError(''.join(('MusicCast zone <', mcid, '> not found in device <',
                                           self.id, '>.')))
コード例 #11
0
    def _transform_arg(self, key, invalue=None, mcvalue=None):
        '''Transforms a message argument from/to internal to/from MusicCast.

        Args:

            key (string): internal name of argument.

            invalue: the internal value to be transformed; if provided the transformation
              is done from this value to the MusicCast value, which is returned.

            mcvalue: the MusicCast value to be transformed; relevant only if ``invalue`` is
              None, in which case the transformation is done from this value to the
              internal value, which is returned.

        Returns:
            string: the transformed representation of the value.
        '''
        # TODO: do we need to transform the keys as well?
        try:
            func = TRANSFORM_ARG[key]  # Retrieve the transformation lambdas
        except KeyError:
            raise mcx.LogicError(''.join(
                ('Argument ', str(key), ' has no transformation.')))
        if invalue is not None:  # transform from internal to MusicCast
            value = invalue
            trsfrm = 0
        elif mcvalue is not None:  # transform from MusicCast to internal
            value = mcvalue
            trsfrm = 1
        else:
            raise mcx.ConfigError(''.join(
                ('No valid parameters to transform <', str(key), '> on zone <',
                 str(self.id), '> of device <', str(self.device.id), '>.')))
        try:
            trsfrm_value = func[trsfrm](self, value)
        except (TypeError,
                ValueError) as err:  # errors to catch in case of bad format
            raise mcx.LogicError(''.join(
                ('Value ', str(value), ' of argument ', str(key),
                 ' seems of the wrong type. Error:\n\t', str(err))))
        return trsfrm_value
コード例 #12
0
 def __init__(self, device_data, system):
     self.system = system
     self.id = device_data['id']
     self._model = device_data.get('model', None)
     self._protocol = device_data.get('protocol', None)
     self.gateway = device_data.get('gateway', None)
     self.musiccast = (self._protocol == 'YEC')
     if self.musiccast: self._host = device_data['host']
     else: self._host = device_data.get('host', None)
     # Load the feeds, sources and zones from the static data. Zones need to be done at the end.
     feeds = []
     for feed_data in device_data['feeds']: feeds.append(Feed(feed_data, self))
     self.feeds = tuple(feeds)
     sources = []
     for source_data in device_data['sources']: sources.append(Source(source_data, self))
     self.sources = tuple(sources)
     self.inputs = self.feeds + self.sources
     if not self.inputs:
         raise mcx.ConfigError(''.join(('Device <', self.id, '> has no inputs.')))
     self._sources_d = {src.id:src for src in self.sources}
     self._inputs_d = {src.id:src for src in self.sources}
     self._inputs_d.update({feed.id:feed for feed in self.feeds})
     zones = []
     for zone_data in device_data['zones']: zones.append(Zone(zone_data, self))
     self.zones = tuple(zones)
     # MusicCast related attributes
     if self.musiccast:  # this is a MusicCast device
         self._ready = False
         self._load_time = 0 # time when load_musiccast was last called
         self.conn = mcc.musiccastComm(self._host, self.system.listen_port)
         self._dev_info = None
         self._features = None
         # dictionaries to help the event processor, initialised in load_musiccast
         self._mcinfotype_d = {}
         self._mczone_d = {}
         # refresh related attribute
         self._zone_index = 0
         self._zone_num = len(self.zones)
     return
コード例 #13
0
    def init_infotype(self, play_info_type):
        ''' Returns a new or an existing instance of PlayInfoType.

        For each type there is only one instance of the corresponding class.

        Args:
            play_info_type (string): one of **tuner**, **cd**, or **netusb**.

        Raises:
            ConfigError: if the play_info_type is not recognised.
        '''
        if play_info_type in self._mcinfotype_d:
            return self._mcinfotype_d[play_info_type]
        if play_info_type == 'tuner':
            self._mcinfotype_d[play_info_type] = Tuner(self)
            return self._mcinfotype_d[play_info_type]
        elif play_info_type == 'cd':
            self._mcinfotype_d[play_info_type] = CD(self)
            return self._mcinfotype_d[play_info_type]
        elif play_info_type == 'netusb':
            self._mcinfotype_d[play_info_type] = NetUSB(self)
            return self._mcinfotype_d[play_info_type]
        else:
            raise mcx.ConfigError(''.join(('PlayInfoType <', play_info_type, '> does not exist.')))
コード例 #14
0
    def get_device(self, device_id=None, yxc_id=None, raises=False):
        ''' Returns the :class:`Device` object from its id or Yamaha id.

        Args:
            device_id (string): the id of the device sought
            yxc_id (string): the Yamaha hardware id of the device sought
            raises (boolean): if True, raises an exception instead of returning ``False``
        '''
        if device_id:
            try:
                return self._devices_by_id[device_id]
            except KeyError:
                err = ''.join(('Device id <', str(device_id), '> not found.'))
        elif yxc_id:
            for dev in self.devices:
                if yxc_id == dev.get_yxcid(raises=False): return dev
            err = ''.join(('Yamaha id <', str(yxc_id), '> not found.'))
            # The code below can be reinstated once the dictionary works
            #try: return self._devices_by_yxcid[yxc_id]
            #except KeyError: err = ''.join(('Yamaha id <', str(yxc_id), '> not found.'))
        else:
            err = 'No valid argument in get_device()'
        if raises: raise mcx.ConfigError(err)
        else: return None
コード例 #15
0
    def listen_musiccast(self):
        ''' Checks if a MusicCast event has arrived and parses it.

        This method uses the dictionary EVENTS based on all possible fields that
        a MusicCast can have (see Yamaha doc for more details).  This
        dictionary has only 2 levels and every *node* is either a **dict** or a
        **callable**.  Any *event* object received from a MusicCast device should
        have a structure which is a subset of the EVENTS one.  The algorithm
        goes through the received *event* structure in parallel of going through
        the EVENTS one.  If there is a key mismatch, the specific key in *event*
        that cannot find a match in EVENTS is ignored.  If there is a key match,
        the lambda function found as value of that key in EVENTS is called with
        the value of that same key found in *event* (the *argument*).

        TODO: check if more than one event could be received in a single call.
        '''
        #event = mcc.get_event()
        event = self._listener.get_event()
        if event is None: return
        # Find device within the event dictionary
        device_id = event.pop('device_id', None)  # read and remove key
        if device_id is None:
            raise mcx.CommsError('Event has no device_id. Ignore.')
        device = self.get_device(yxc_id=device_id, raises=True)
        # Read event dictionary and call lambda for each key match found
        flist = [
        ]  # list of all lambdas to call; the elements are pairs (func, arg)
        for key1 in event:
            try:
                isdict = isinstance(EVENTS[key1], dict)
            except KeyError:
                _logger.info(''.join(
                    ('Event has an unknown item <', str(key1), '>. Ignore.')))
                continue
            if isdict:
                if not isinstance(event[key1], dict):
                    raise mcx.ConfigError(
                        'Unexpected structure of event. Ignore.')
                for key2 in event[key1]:
                    try:
                        func = EVENTS[key1][key2]
                    except KeyError:
                        _logger.info(''.join(('Unknown item in event <',
                                              str(key2), '>. Ignore.')))
                        continue
                    if func is not None:
                        flist.append((func, event[key1][key2]))
            else:
                func = EVENTS[key1]
                if func is not None: flist.append((func, event[key1]))
        # now execute the lambdas
        while True:
            try:
                func, arg = flist.pop(0)
            except IndexError:
                break
            try:
                func(device, arg)
            except mcx.AnyError as err:
                _logger.info(''.join(
                    ('Problem processing event item. Ignore. Error:\n\t',
                     repr(err))))
        return