示例#1
0
    def get_event(self):
        ''' Checks the socket for events broadcasted by the MusicCast devices.

        The 'body' of the event (see below) is in the form:
        ('{"main":{"power":"on"},"device_id":"00A0DED57E83"}', ('192.168.1.44', 38507))
        or:
        ('{"main":{"volume":88},"zone2":{"volume":0}, "device_id":"00A0DED3FD57"}', ('192.168.1.42', 46514))
        '''
        # TODO: check max length of the events and if more than one event could arrive at once
        #if not self._connected: return
        try:
            event = select.select([self._socket], [], [], _SOCKET_TIMEOUT)
        except select.error as err:
            raise mcx.CommsError(''.join(
                ('Socket error: ', str(err[1]))))  # TODO: throttle this?
        if event[0]:  # there is an event
            body = event[0][0].recvfrom(1024)
            _logger.debug(''.join(('Event received: ', str(body))))
            try:
                dict_response = json.loads(body[0])
            except ValueError as err:
                raise mcx.CommsError(''.join(
                    ('The received event is not in JSON format. Error:\n\t',
                     str(err))))
            return dict_response
        return None
示例#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 __init__(self, device):
     super(NetUSB, self).__init__('netusb', device)
     self._preset_info = None
     self._play_time = '0'
     self._play_message = ''
     # load the max_preset
     try: self._max_presets = int(self.device.get_feature(('netusb', 'preset', 'num')))
     except ValueError:
         raise mcx.CommsError('getFeatures item <max_presets> not an int.')
     # Load the preset_info
     self._preset_info = None
     self.update_preset_info()
     return
示例#4
0
    def update_preset_info(self):
        ''' Retrieves the preset_info structure.

        Info type == **tuner**: the request requires a `band` argument that depends on the
        features of the device.  As the structure returned by the request is a list of objects that
        always include the band that the preset relates to, we can concatenate all the preset lists.
        '''

        if self._preset_separate:
            preset_info = []
            for band in self._info_bands:
                response = self.device.conn.mcrequest('tuner',
                                                      ''.join(('getPresetInfo?band=', band)))
                try: preset_info.extend(response['preset_info'])
                except KeyError:
                    raise mcx.CommsError('getPresetInfo did not return a preset_info field.')
            self._preset_info = preset_info # update attribute only after all worked properly
        else:
            response = self.device.conn.mcrequest('tuner', 'getPresetInfo?band=common')
            try: self._preset_info = response['preset_info']
            except KeyError:
                raise mcx.CommsError('getPresetInfo did not return a preset_info field.')
        return
示例#5
0
 def __init__(self, port):
     self._port = port
     self._connected = False
     self._socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
     try:
         self._socket.bind(('', self._port))
         self._socket.setblocking(0)
         self._connected = True
     except socket.error as err:
         self._socket.close()
         self._connected = False
         raise mcx.CommsError(''.join(
             ('Can\'t open listener socket. Error:\n\t', str(err))))
     return
示例#6
0
    def load_musiccast(self):
        '''Initialisation of MusicCast related characteristics.

        This method uses the objects retrieved from previous HTTP requests.
        It can be called at any time to try again this initialisation.
        '''
        try:
            for inp in self.device.get_feature(('system', 'input_list')):
                if inp['id'] == self.mcid: play_info_type = inp['play_info_type']
        except KeyError:
            mcx.CommsError('getFeatures object does not contain the keys '\
                           '<system>, <input_list>, <id> or <play_info_type>.')
        self.playinfo_type = self.device.init_infotype(play_info_type)
        return
示例#7
0
    def _get_dict_item(self, dico, item):
        ''' Retrieves the item in the dictionary.

        This is a safety method in case a structure sent back by MusicCast
        does not have the item expected.  It catches the KeyError exception
        and changes it into a CommsError one.

        Args:
            dico (dict): the dictionary to look into
            item (string): the key to look for
        '''
        try:
            return dico[item]
        except KeyError:
            raise mcx.CommsError(''.join(
                ('The dictionary provided by device <', self.device.id,
                 '> does not contain the item <', item, '>')))
示例#8
0
    def mcrequest(self, qualifier, mc_command):
        ''' Sends a single HTTP request and returns the response.

        This method sends the request and read the response step by step in
        order to catch properly any error in the process. Currently the requests
        are always with method = 'GET' and version = 'v1'.

        Args:
            qualifier (string): the token in the MusicCast syntax representing
                either a zone or a source, depending on the type of command
                sent;
            mc_command (string): the command to send at the end of the request;
                it has to include any extra argument if there are any.

        Raises:
            commsError: in case of any form of Communication Error with the device.

        Returns:
            dictionary: the dictionary equivalent of the JSON structure sent back as a reply
                from the device.
        '''

        conn = httplib.HTTPConnection(self._host, timeout=self._timeout)

        _logger.debug(''.join(
            ('Sending to address <', self._host, '> the request: ', '/'.join(
                ('/YamahaExtendedControl/v1', qualifier, mc_command)))))

        try:
            conn.request(method='GET',
                         url='/'.join(('/YamahaExtendedControl/v1', qualifier,
                                       mc_command)),
                         headers=self._headers)
        except httplib.HTTPException as err:
            conn.close()
            raise mcx.CommsError(''.join(
                ('Can\'t send request. Error:\n\t', str(err))))
        except socket.timeout:
            conn.close()
            raise mcx.CommsError('Can\'t send request. Connection timed-out.')
        except socket.error as err:
            conn.close()
            raise mcx.CommsError(''.join(
                ('Can\'t send request. Socket error:\n\t', str(err))))

        # insert a delay here?

        try:
            response = conn.getresponse()
        except httplib.HTTPException as err:
            conn.close()
            raise mcx.CommsError(''.join(
                ('Can\'t get response. Error:\n\t', str(err))))
        except socket.timeout:
            conn.close()
            raise mcx.CommsError('Can\'t get response. Connection timed-out.')
        except socket.error as err:
            conn.close()
            raise mcx.CommsError(''.join(
                ('Can\'t get response. Socket error:\n\t', str(err))))

        if response.status != 200:
            conn.close()
            raise mcx.CommsError(''.join(('HTTP response status not OK.'\
                                          '\n\tStatus: ', httplib.responses[response.status],
                                          '\n\tReason: ', response.reason)))

        try:
            dict_response = json.loads(response.read())
        except ValueError as err:
            conn.close()
            raise mcx.CommsError(''.join(('The response from the device is not'\
                                          ' in JSON format. Error:\n\t', str(err))))

        if dict_response['response_code'] != 0:
            conn.close()
            raise mcx.CommsError(''.join(('The response code from the'\
                                          ' MusicCast device is not OK. Actual code:\n\t',
                                          str(dict_response['response_code']))))

        _logger.debug('Request answered successfully.')

        conn.close()
        self.request_time = time.time()
        return dict_response
示例#9
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