Пример #1
0
    def update_devices():
        """Ping Alexa API to identify all devices, bluetooth, and last called device.

        This will add new devices and services when discovered. By default this
        runs every SCAN_INTERVAL seconds unless another method calls it. While
        throttled at MIN_TIME_BETWEEN_SCANS, care should be taken to reduce the
        number of runs to avoid flooding. Slow changing states should be
        checked here instead of in spawned components like media_player since
        this object is one per account.
        Each AlexaAPI call generally results in one webpage request.
        """
        from alexapy import AlexaAPI
        existing_serials = (hass.data[DATA_ALEXAMEDIA]['accounts'][email]
                            ['entities']['media_player'].keys())
        existing_entities = (hass.data[DATA_ALEXAMEDIA]['accounts'][email]
                             ['entities']['media_player'].values())
        devices = AlexaAPI.get_devices(login_obj)
        bluetooth = AlexaAPI.get_bluetooth(login_obj)
        _LOGGER.debug("%s: Found %s devices, %s bluetooth", hide_email(email),
                      len(devices) if devices is not None else '',
                      len(bluetooth) if bluetooth is not None else '')
        if ((devices is None or bluetooth is None)
                and not hass.data[DOMAIN]['accounts'][email]['config']):
            _LOGGER.debug("Alexa API disconnected; attempting to relogin")
            login_obj.login_with_cookie()
            test_login_status(hass, config, login_obj, setup_platform_callback)
            return

        new_alexa_clients = []  # list of newly discovered device names
        excluded = []
        included = []
        for device in devices:
            if include and device['accountName'] not in include:
                included.append(device['accountName'])
                continue
            elif exclude and device['accountName'] in exclude:
                excluded.append(device['accountName'])
                continue

            for b_state in bluetooth['bluetoothStates']:
                if device['serialNumber'] == b_state['deviceSerialNumber']:
                    device['bluetooth_state'] = b_state

            (hass.data[DATA_ALEXAMEDIA]['accounts'][email]['devices']
             ['media_player'][device['serialNumber']]) = device

            if device['serialNumber'] not in existing_serials:
                new_alexa_clients.append(device['accountName'])
        _LOGGER.debug(
            "%s: Existing: %s New: %s;"
            " Filtered by: include_devices: %s exclude_devices:%s",
            hide_email(email), list(existing_entities), new_alexa_clients,
            included, excluded)

        if new_alexa_clients:
            for component in ALEXA_COMPONENTS:
                load_platform(hass, component, DOMAIN, {}, config)

        # Process last_called data to fire events
        update_last_called(login_obj)
Пример #2
0
    def __init__(self, device, login, hass):
        """Initialize the Alexa device."""
        from alexapy import AlexaAPI

        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(self, login)
        self.auth = AlexaAPI.get_authentication(login)
        self.alexa_api_session = login.session
        self.account = hide_email(login.email)

        # Logged in info
        self._authenticated = None
        self._can_access_prime_music = None
        self._customer_email = None
        self._customer_id = None
        self._customer_name = None
        self._set_authentication_details(self.auth)

        # Device info
        self._device = None
        self._device_name = None
        self._device_serial_number = None
        self._device_type = None
        self._device_family = None
        self._device_owner_customer_id = None
        self._software_version = None
        self._available = None
        self._capabilities = []
        self._cluster_members = []
        self._locale = None
        # Media
        self._session = None
        self._media_duration = None
        self._media_image_url = None
        self._media_title = None
        self._media_pos = None
        self._media_album_name = None
        self._media_artist = None
        self._media_player_state = None
        self._media_is_muted = None
        self._media_vol_level = None
        self._previous_volume = None
        self._source = None
        self._source_list = []
        self._shuffle = None
        self._repeat = None
        # Last Device
        self._last_called = None
        # Do not Disturb state
        self._dnd = None
        # Polling state
        self._should_poll = True
        self._last_update = 0
        self.refresh(device)
        # Register event handler on bus
        hass.bus.listen(('{}_{}'.format(ALEXA_DOMAIN,
                                        hide_email(login.email)))[0:32],
                        self._handle_event)
Пример #3
0
    def __init__(self, device, login) -> None:
        # pylint: disable=unexpected-keyword-arg
        """Initialize the Alexa device."""

        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(device, login)
        self.email = login.email
        self.account = hide_email(login.email)
Пример #4
0
    def update_devices():
        """Ping Alexa API to identify all devices, bluetooth, and last called device.

        This will add new devices and services when discovered. By default this
        runs every SCAN_INTERVAL seconds unless another method calls it. While
        throttled at MIN_TIME_BETWEEN_SCANS, care should be taken to reduce the
        number of runs to avoid flooding. Slow changing states should be
        checked here instead of in spawned components like media_player since
        this object is one per account.
        Each AlexaAPI call generally results in one webpage request.
        """
        from alexapy import AlexaAPI
        email = login_obj.get_email()
        _LOGGER.debug("Updating devices for %s", hide_email(email))
        devices = AlexaAPI.get_devices(login_obj)
        bluetooth = AlexaAPI.get_bluetooth(login_obj)
        last_called = AlexaAPI.get_last_device_serial(login_obj)
        _LOGGER.debug("Found %s devices, %s bluetooth, last_called: %s",
                      len(devices), len(bluetooth), last_called)
        if ((devices is None or bluetooth is None) and len(_CONFIGURING) == 0):
            _LOGGER.debug("Alexa API disconnected; attempting to relogin")
            login_obj.login_with_cookie()
            testLoginStatus(hass, config, login_obj, setup_platform_callback)

        new_alexa_clients = []  # list of newly discovered device jsons
        available_client_ids = []  # list of known serial numbers
        for device in devices:
            if include and device['accountName'] not in include:
                continue
            elif exclude and device['accountName'] in exclude:
                continue

            for b_state in bluetooth['bluetoothStates']:
                if device['serialNumber'] == b_state['deviceSerialNumber']:
                    device['bluetooth_state'] = b_state

            available_client_ids.append(device['serialNumber'])
            (hass.data[DATA_ALEXAMEDIA]['accounts'][email]['devices']
             ['media_player'][device['serialNumber']]) = device

            if device['serialNumber'] not in alexa_clients:
                new_alexa_clients.append(device)

        if new_alexa_clients:
            for component in ALEXA_COMPONENTS:
                load_platform(hass, component, DOMAIN, {}, config)
        # Process last_called data to fire events
        stored_data = hass.data[DATA_ALEXAMEDIA]['accounts'][email]
        if (('last_called' in stored_data
             and last_called != stored_data['last_called']) or
            ('last_called' not in stored_data and last_called is not None)):
            hass.bus.fire('{}_{}'.format(DOMAIN, email),
                          {'last_called_change': last_called})
        (hass.data[DATA_ALEXAMEDIA]['accounts'][email]['last_called']
         ) = AlexaAPI.get_last_device_serial(login_obj)
Пример #5
0
    def __init__(self, device, login):
        """Initialize the Alexa device."""
        from alexapy import AlexaAPI

        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(self, login)
        self.auth = None
        self.alexa_api_session = login.session
        self.account = hide_email(login.email)

        # Logged in info
        self._authenticated = None
        self._can_access_prime_music = None
        self._customer_email = None
        self._customer_id = None
        self._customer_name = None

        # Device info
        self._device = None
        self._device_name = None
        self._device_serial_number = None
        self._device_type = None
        self._device_family = None
        self._device_owner_customer_id = None
        self._software_version = None
        self._available = None
        self._capabilities = []
        self._cluster_members = []
        self._locale = None
        # Media
        self._session = None
        self._media_duration = None
        self._media_image_url = None
        self._media_title = None
        self._media_pos = None
        self._media_album_name = None
        self._media_artist = None
        self._media_player_state = None
        self._media_is_muted = None
        self._media_vol_level = None
        self._previous_volume = None
        self._source = None
        self._source_list = []
        self._shuffle = None
        self._repeat = None
        # Last Device
        self._last_called = None
        # Do not Disturb state
        self._dnd = None
        # Polling state
        self._should_poll = True
        self._last_update = 0
Пример #6
0
    def update_last_called(login_obj, last_called=None):
        """Update the last called device for the login_obj.

        This will store the last_called in hass.data and also fire an event
        to notify listeners.
        """
        from alexapy import AlexaAPI
        if last_called:
            last_called = last_called
        else:
            last_called = AlexaAPI.get_last_device_serial(login_obj)
        _LOGGER.debug("%s: Updated last_called: %s", hide_email(email),
                      hide_serial(last_called))
        stored_data = hass.data[DATA_ALEXAMEDIA]['accounts'][email]
        if (('last_called' in stored_data
             and last_called != stored_data['last_called']) or
            ('last_called' not in stored_data and last_called is not None)):
            _LOGGER.debug(
                "%s: last_called changed: %s to %s", hide_email(email),
                hide_serial(stored_data['last_called'] if 'last_called' in
                            stored_data else None), hide_serial(last_called))
            hass.bus.fire(('{}_{}'.format(DOMAIN, hide_email(email)))[0:32],
                          {'last_called_change': last_called})
        (hass.data[DATA_ALEXAMEDIA]['accounts'][email]['last_called']
         ) = last_called
Пример #7
0
class AlexaMedia:
    """Implementation of Alexa Media Base object."""

    def __init__(self, device, login) -> None:
        # pylint: disable=unexpected-keyword-arg
        """Initialize the Alexa device."""

        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(device, login)
        self.email = login.email
        self.account = hide_email(login.email)

    def check_login_changes(self):
        """Update Login object if it has changed."""
        # _LOGGER.debug("Checking if Login object has changed")
        try:
            login = self.hass.data[DATA_ALEXAMEDIA]["accounts"][self.email]["login_obj"]
        except (AttributeError, KeyError):
            return
        # _LOGGER.debug("Login object %s closed status: %s", login, login.session.closed)
        # _LOGGER.debug(
        #     "Alexaapi %s closed status: %s",
        #     self.alexa_api,
        #     self.alexa_api._session.closed,
        # )
        if self.alexa_api.update_login(login):
            _LOGGER.debug("Login object has changed; updating")
            self._login = login
            self.email = login.email
            self.account = hide_email(login.email)
    def __init__(self, login, hass):
        # pylint: disable=unexpected-keyword-arg
        """Initialize the Alexa device."""
        from alexapy import AlexaAPI
        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(self, login)
        self.alexa_api_session = login.session
        self.account = hide_email(login.email)
        self.hass = hass

        # Guard info
        self._appliance_id = None
        self._guard_entity_id = None
        self._friendly_name = "Alexa Guard"
        self._state = None
        self._should_poll = False
        self._attrs = {}

        try:
            from simplejson import JSONDecodeError
            data = self.alexa_api.get_guard_details(self._login)
            guard_dict = (
                data['locationDetails']['locationDetails']['Default_Location']
                ['amazonBridgeDetails']['amazonBridgeDetails']
                ['LambdaBridge_AAA/OnGuardSmartHomeBridgeService']
                ['applianceDetails']['applianceDetails'])
        except (KeyError, TypeError, JSONDecodeError):
            guard_dict = {}
        for key, value in guard_dict.items():
            if value['modelName'] == "REDROCK_GUARD_PANEL":
                self._appliance_id = value['applianceId']
                self._guard_entity_id = value['entityId']
                self._friendly_name += " " + self._appliance_id[-5:]
                _LOGGER.debug("%s: Discovered %s: %s %s", self.account,
                              self._friendly_name, self._appliance_id,
                              self._guard_entity_id)
        if not self._appliance_id:
            _LOGGER.debug("%s: No Alexa Guard entity found", self.account)
            return None
        # Register event handler on bus
        hass.bus.listen(('{}_{}'.format(ALEXA_DOMAIN,
                                        hide_email(login.email)))[0:32],
                        self._handle_event)
        self.refresh(no_throttle=True)
Пример #9
0
    def update_bluetooth_state(login_obj, device_serial):
        """Update the bluetooth state on ws bluetooth event."""
        from alexapy import AlexaAPI
        bluetooth = AlexaAPI.get_bluetooth(login_obj)
        device = (hass.data[DATA_ALEXAMEDIA]['accounts'][email]['devices']
                  ['media_player'][device_serial])

        for b_state in bluetooth['bluetoothStates']:
            if device_serial == b_state['deviceSerialNumber']:
                device['bluetooth_state'] = b_state
        return device['bluetooth_state']
Пример #10
0
    def check_login_changes(self):
        """Update Login object if it has changed."""
        try:
            login = self.hass.data[DATA_ALEXAMEDIA]["accounts"][self.email]["login_obj"]
        except (AttributeError, KeyError):
            return
        if self._login != login or self._login.session != login.session:
            from alexapy import AlexaAPI

            _LOGGER.debug("Login object has changed; updating")
            self._login = login
            self.alexa_api = AlexaAPI(self, login)
            self.email = login.email
            self.account = hide_email(login.email)
Пример #11
0
    def __init__(self, login) -> None:
        # pylint: disable=unexpected-keyword-arg
        """Initialize the Alexa device."""
        from alexapy import AlexaAPI
        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(self, login)
        self.alexa_api_session = login.session
        self.account = hide_email(login.email)

        # Guard info
        self._appliance_id = None
        self._guard_entity_id = None
        self._friendly_name = "Alexa Guard"
        self._state = None
        self._should_poll = False
        self._attrs = {}
Пример #12
0
    def __init__(self, login, media_players=None) -> None:
        # pylint: disable=unexpected-keyword-arg
        """Initialize the Alexa device."""
        from alexapy import AlexaAPI

        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(self, login)
        self.email = login.email
        self.account = hide_email(login.email)
        self._available = None
        self._assumed_state = None

        # Guard info
        self._appliance_id = None
        self._guard_entity_id = None
        self._friendly_name = "Alexa Guard"
        self._state = None
        self._should_poll = False
        self._attrs: Dict[Text, Text] = {}
        self._media_players = {} or media_players
Пример #13
0
class AlexaClient(MediaPlayerDevice):
    """Representation of a Alexa device."""
    def __init__(self, device, login, hass):
        """Initialize the Alexa device."""
        from alexapy import AlexaAPI

        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(self, login)
        self.auth = AlexaAPI.get_authentication(login)
        self.alexa_api_session = login.session
        self.account = hide_email(login.email)

        # Logged in info
        self._authenticated = None
        self._can_access_prime_music = None
        self._customer_email = None
        self._customer_id = None
        self._customer_name = None
        self._set_authentication_details(self.auth)

        # Device info
        self._device = None
        self._device_name = None
        self._device_serial_number = None
        self._device_type = None
        self._device_family = None
        self._device_owner_customer_id = None
        self._software_version = None
        self._available = None
        self._capabilities = []
        self._cluster_members = []
        self._locale = None
        # Media
        self._session = None
        self._media_duration = None
        self._media_image_url = None
        self._media_title = None
        self._media_pos = None
        self._media_album_name = None
        self._media_artist = None
        self._media_player_state = None
        self._media_is_muted = None
        self._media_vol_level = None
        self._previous_volume = None
        self._source = None
        self._source_list = []
        # Last Device
        self._last_called = None
        # Polling state
        self._should_poll = True
        self._last_update = 0
        self.refresh(device)
        # Register event handler on bus
        hass.bus.listen(('{}_{}'.format(ALEXA_DOMAIN,
                                        hide_email(login.email)))[0:32],
                        self._handle_event)

    def _handle_event(self, event):
        """Handle events.

        This will update last_called and player_state events.
        Each MediaClient reports if it's the last_called MediaClient and will
        listen for HA events to determine it is the last_called.
        When polling instead of websockets, all devices on same account will
        update to handle starting music with other devices. If websocket is on
        only the updated alexa will update.
        Last_called events are only sent if it's a new device or timestamp.
        Without polling, we must schedule the HA update manually.
        https://developers.home-assistant.io/docs/en/entity_index.html#subscribing-to-updates
        The difference between self.update and self.schedule_update_ha_state
        is self.update will pull data from Amazon, while schedule_update
        assumes the MediaClient state is already updated.
        """
        if 'last_called_change' in event.data:
            if (event.data['last_called_change']['serialNumber'] ==
                    self.device_serial_number):
                _LOGGER.debug("%s is last_called: %s", self.name,
                              hide_serial(self.device_serial_number))
                self._last_called = True
            else:
                self._last_called = False
            if (self.hass and self.schedule_update_ha_state):
                email = self._login.email
                force_refresh = not (self.hass.data[DATA_ALEXAMEDIA]
                                     ['accounts'][email]['websocket'])
                self.schedule_update_ha_state(force_refresh=force_refresh)
        elif 'bluetooth_change' in event.data:
            if (event.data['bluetooth_change']['deviceSerialNumber'] ==
                    self.device_serial_number):
                self._bluetooth_state = event.data['bluetooth_change']
                self._source = self._get_source()
                self._source_list = self._get_source_list()
                if (self.hass and self.schedule_update_ha_state):
                    self.schedule_update_ha_state()
        elif 'player_state' in event.data:
            player_state = event.data['player_state']
            if (player_state['dopplerId']['deviceSerialNumber'] ==
                    self.device_serial_number):
                if 'audioPlayerState' in player_state:
                    _LOGGER.debug("%s state update: %s", self.name,
                                  player_state['audioPlayerState'])
                    self.update()  # refresh is necessary to pull all data
                elif 'volumeSetting' in player_state:
                    _LOGGER.debug("%s volume updated: %s", self.name,
                                  player_state['volumeSetting'])
                    self._media_vol_level = player_state['volumeSetting'] / 100
                    if (self.hass and self.schedule_update_ha_state):
                        self.schedule_update_ha_state()
                elif 'dopplerConnectionState' in player_state:
                    self._available = (
                        player_state['dopplerConnectionState'] == "ONLINE")
                    if (self.hass and self.schedule_update_ha_state):
                        self.schedule_update_ha_state()

    def _clear_media_details(self):
        """Set all Media Items to None."""
        # General
        self._media_duration = None
        self._media_image_url = None
        self._media_title = None
        self._media_pos = None
        self._media_album_name = None
        self._media_artist = None
        self._media_player_state = None
        self._media_is_muted = None
        self._media_vol_level = None

    def _set_authentication_details(self, auth):
        """Set Authentication based off auth."""
        self._authenticated = auth['authenticated']
        self._can_access_prime_music = auth['canAccessPrimeMusicContent']
        self._customer_email = auth['customerEmail']
        self._customer_id = auth['customerId']
        self._customer_name = auth['customerName']

    @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
    def refresh(self, device=None):
        """Refresh device data.

        This is a per device refresh and for many Alexa devices can result in
        many refreshes from each individual device. This will call the
        AlexaAPI directly.

        Args:
        device (json): A refreshed device json from Amazon. For efficiency,
                       an individual device does not refresh if it's reported
                       as offline.
        """
        if device is not None:
            self._device = device
            self._device_name = device['accountName']
            self._device_family = device['deviceFamily']
            self._device_type = device['deviceType']
            self._device_serial_number = device['serialNumber']
            self._device_owner_customer_id = device['deviceOwnerCustomerId']
            self._software_version = device['softwareVersion']
            self._available = device['online']
            self._capabilities = device['capabilities']
            self._cluster_members = device['clusterMembers']
            self._bluetooth_state = device['bluetooth_state']
            self._locale = device['locale'] if 'locale' in device else 'en-US'
        if self._available is True:
            _LOGGER.debug("%s: Refreshing %s", self.account, self.name)
            self._source = self._get_source()
            self._source_list = self._get_source_list()
            self._last_called = self._get_last_called()
            session = self.alexa_api.get_state()
        else:
            session = None
        self._clear_media_details()
        # update the session if it exists; not doing relogin here
        if session is not None:
            self._session = session
        if self._session is None:
            return
        if 'playerInfo' in self._session:
            self._session = self._session['playerInfo']
            if self._session['state'] is not None:
                self._media_player_state = self._session['state']
                self._media_pos = (
                    self._session['progress']['mediaProgress'] if
                    (self._session['progress'] is not None
                     and 'mediaProgress' in self._session['progress']) else
                    None)
                self._media_is_muted = (self._session['volume']['muted'] if (
                    self._session['volume'] is not None
                    and 'muted' in self._session['volume']) else None)
                self._media_vol_level = (
                    self._session['volume']['volume'] / 100 if
                    (self._session['volume'] is not None
                     and 'volume' in self._session['volume']) else None)
                self._media_title = (self._session['infoText']['title'] if
                                     (self._session['infoText'] is not None
                                      and 'title' in self._session['infoText'])
                                     else None)
                self._media_artist = (
                    self._session['infoText']['subText1'] if
                    (self._session['infoText'] is not None
                     and 'subText1' in self._session['infoText']) else None)
                self._media_album_name = (
                    self._session['infoText']['subText2'] if
                    (self._session['infoText'] is not None
                     and 'subText2' in self._session['infoText']) else None)
                self._media_image_url = (self._session['mainArt']['url'] if (
                    self._session['mainArt'] is not None
                    and 'url' in self._session['mainArt']) else None)
                self._media_duration = (
                    self._session['progress']['mediaLength'] if
                    (self._session['progress'] is not None
                     and 'mediaLength' in self._session['progress']) else None)

    @property
    def source(self):
        """Return the current input source."""
        return self._source

    @property
    def source_list(self):
        """List of available input sources."""
        return self._source_list

    def select_source(self, source):
        """Select input source."""
        if source == 'Local Speaker':
            self.alexa_api.disconnect_bluetooth()
            self._source = 'Local Speaker'
        elif self._bluetooth_state['pairedDeviceList'] is not None:
            for devices in self._bluetooth_state['pairedDeviceList']:
                if devices['friendlyName'] == source:
                    self.alexa_api.set_bluetooth(devices['address'])
                    self._source = source

    def _get_source(self):
        source = 'Local Speaker'
        if self._bluetooth_state['pairedDeviceList'] is not None:
            for device in self._bluetooth_state['pairedDeviceList']:
                if device['connected'] is True:
                    return device['friendlyName']
        return source

    def _get_source_list(self):
        sources = []
        if self._bluetooth_state['pairedDeviceList'] is not None:
            for devices in self._bluetooth_state['pairedDeviceList']:
                if (devices['profiles']
                        and 'A2DP-SOURCE' in devices['profiles']):
                    sources.append(devices['friendlyName'])
        return ['Local Speaker'] + sources

    def _get_last_called(self):
        last_called_serial = (None if self.hass is None else (
            self.hass.data[DATA_ALEXAMEDIA]['accounts'][
                self._login.email]['last_called']['serialNumber']))
        _LOGGER.debug("%s: Last_called check: self: %s reported: %s",
                      self._device_name,
                      hide_serial(self._device_serial_number),
                      hide_serial(last_called_serial))
        if (last_called_serial is not None
                and self._device_serial_number == last_called_serial):
            return True
        return False

    @property
    def available(self):
        """Return the availability of the client."""
        return self._available

    @property
    def unique_id(self):
        """Return the id of this Alexa client."""
        return self.device_serial_number

    @property
    def name(self):
        """Return the name of the device."""
        return self._device_name

    @property
    def device_serial_number(self):
        """Return the machine identifier of the device."""
        return self._device_serial_number

    @property
    def device(self):
        """Return the device, if any."""
        return self._device

    @property
    def session(self):
        """Return the session, if any."""
        return self._session

    @property
    def state(self):
        """Return the state of the device."""
        if self._media_player_state == 'PLAYING':
            return STATE_PLAYING
        if self._media_player_state == 'PAUSED':
            return STATE_PAUSED
        if self._media_player_state == 'IDLE':
            return STATE_IDLE
        return STATE_STANDBY

    def update(self):
        """Get the latest details on a media player.

        Because media players spend the majority of time idle, an adaptive
        update should be used to avoid flooding Amazon focusing on known
        play states. An initial version included an update_devices call on
        every update. However, this quickly floods the network for every new
        device added. This should only call refresh() to call the AlexaAPI.
        """
        if (self._device is None or self.entity_id is None):
            # Device has not initialized yet
            return
        email = self._login.email
        device = (self.hass.data[DATA_ALEXAMEDIA]['accounts'][email]['devices']
                  ['media_player'][self.unique_id])
        self.refresh(
            device,  # pylint: disable=unexpected-keyword-arg
            no_throttle=True)
        if (self.state in [STATE_PLAYING] and
                #  only enable polling if websocket not connected
            (not self.hass.data[DATA_ALEXAMEDIA]['accounts'][email]
             ['websocket'])):
            self._should_poll = False  # disable polling since manual update
            if (self._last_update == 0 or util.dt.as_timestamp(util.utcnow()) -
                    util.dt.as_timestamp(self._last_update) >
                    PLAY_SCAN_INTERVAL):
                _LOGGER.debug("%s playing; scheduling update in %s seconds",
                              self.name, PLAY_SCAN_INTERVAL)
                call_later(
                    self.hass, PLAY_SCAN_INTERVAL, lambda _: self.
                    schedule_update_ha_state(force_refresh=True))
        elif self._should_poll:  # Not playing, one last poll
            self._should_poll = False
            if not (self.hass.data[DATA_ALEXAMEDIA]['accounts'][email]
                    ['websocket']):
                _LOGGER.debug(
                    "Disabling polling and scheduling last update in"
                    " 300 seconds for %s", self.name)
                call_later(
                    self.hass, 300, lambda _: self.schedule_update_ha_state(
                        force_refresh=True))
            else:
                _LOGGER.debug("Disabling polling for %s", self.name)
        self._last_update = util.utcnow()
        self.schedule_update_ha_state()

    @property
    def media_content_type(self):
        """Return the content type of current playing media."""
        if self.state in [STATE_PLAYING, STATE_PAUSED]:
            return MEDIA_TYPE_MUSIC
        return STATE_STANDBY

    @property
    def media_artist(self):
        """Return the artist of current playing media, music track only."""
        return self._media_artist

    @property
    def media_album_name(self):
        """Return the album name of current playing media, music track only."""
        return self._media_album_name

    @property
    def media_duration(self):
        """Return the duration of current playing media in seconds."""
        return self._media_duration

    @property
    def media_position(self):
        """Return the duration of current playing media in seconds."""
        return self._media_pos

    @property
    def media_position_updated_at(self):
        """When was the position of the current playing media valid."""
        return self._last_update

    @property
    def media_image_url(self):
        """Return the image URL of current playing media."""
        return self._media_image_url

    @property
    def media_title(self):
        """Return the title of current playing media."""
        return self._media_title

    @property
    def device_family(self):
        """Return the make of the device (ex. Echo, Other)."""
        return self._device_family

    @property
    def supported_features(self):
        """Flag media player features that are supported."""
        return SUPPORT_ALEXA

    def set_volume_level(self, volume):
        """Set volume level, range 0..1."""
        if not self.available:
            return
        self.alexa_api.set_volume(volume)
        self._media_vol_level = volume
        if not (self.hass.data[DATA_ALEXAMEDIA]['accounts'][self._login.email]
                ['websocket']):
            self.update()

    @property
    def volume_level(self):
        """Return the volume level of the client (0..1)."""
        return self._media_vol_level

    @property
    def is_volume_muted(self):
        """Return boolean if volume is currently muted."""
        if self.volume_level == 0:
            return True
        return False

    def mute_volume(self, mute):
        """Mute the volume.

        Since we can't actually mute, we'll:
        - On mute, store volume and set volume to 0
        - On unmute, set volume to previously stored volume
        """
        if not (self.state == STATE_PLAYING and self.available):
            return

        self._media_is_muted = mute
        if mute:
            self._previous_volume = self.volume_level
            self.alexa_api.set_volume(0)
        else:
            if self._previous_volume is not None:
                self.alexa_api.set_volume(self._previous_volume)
            else:
                self.alexa_api.set_volume(50)
        if not (self.hass.data[DATA_ALEXAMEDIA]['accounts'][self._login.email]
                ['websocket']):
            self.update()

    def media_play(self):
        """Send play command."""
        if not (self.state in [STATE_PLAYING, STATE_PAUSED]
                and self.available):
            return
        self.alexa_api.play()
        if not (self.hass.data[DATA_ALEXAMEDIA]['accounts'][self._login.email]
                ['websocket']):
            self.update()

    def media_pause(self):
        """Send pause command."""
        if not (self.state in [STATE_PLAYING, STATE_PAUSED]
                and self.available):
            return
        self.alexa_api.pause()
        if not (self.hass.data[DATA_ALEXAMEDIA]['accounts'][self._login.email]
                ['websocket']):
            self.update()

    def turn_off(self):
        """Turn the client off.

        While Alexa's do not have on/off capability, we can use this as another
        trigger to do updates. For turning off, we can clear media_details.
        """
        self._should_poll = False
        self.media_pause()
        self._clear_media_details()

    def turn_on(self):
        """Turn the client on.

        While Alexa's do not have on/off capability, we can use this as another
        trigger to do updates.
        """
        self._should_poll = True
        self.media_pause()

    def media_next_track(self):
        """Send next track command."""
        if not (self.state in [STATE_PLAYING, STATE_PAUSED]
                and self.available):
            return
        self.alexa_api.next()
        if not (self.hass.data[DATA_ALEXAMEDIA]['accounts'][self._login.email]
                ['websocket']):
            self.update()

    def media_previous_track(self):
        """Send previous track command."""
        if not (self.state in [STATE_PLAYING, STATE_PAUSED]
                and self.available):
            return
        self.alexa_api.previous()
        if not (self.hass.data[DATA_ALEXAMEDIA]['accounts'][self._login.email]
                ['websocket']):
            self.update()

    def send_tts(self, message):
        """Send TTS to Device.

        NOTE: Does not work on WHA Groups.
        """
        self.alexa_api.send_tts(message, customer_id=self._customer_id)

    def send_announcement(self, message, **kwargs):
        """Send announcement to the media player."""
        self.alexa_api.send_announcement(message,
                                         customer_id=self._customer_id,
                                         **kwargs)

    def send_mobilepush(self, message, **kwargs):
        """Send push to the media player's associated mobile devices."""
        self.alexa_api.send_mobilepush(message,
                                       customer_id=self._customer_id,
                                       **kwargs)

    def play_media(self, media_type, media_id, enqueue=None, **kwargs):
        """Send the play_media command to the media player."""
        if media_type == "music":
            self.alexa_api.send_tts("Sorry, text to speech can only be called "
                                    " with the media player alexa tts service")
        elif media_type == "sequence":
            self.alexa_api.send_sequence(media_id,
                                         customer_id=self._customer_id,
                                         **kwargs)
        elif media_type == "routine":
            self.alexa_api.run_routine(media_id)
        else:
            self.alexa_api.play_music(media_type,
                                      media_id,
                                      customer_id=self._customer_id,
                                      **kwargs)
        if not (self.hass.data[DATA_ALEXAMEDIA]['accounts'][self._login.email]
                ['websocket']):
            self.update()

    @property
    def device_state_attributes(self):
        """Return the scene state attributes."""
        attr = {'available': self._available, 'last_called': self._last_called}
        return attr

    @property
    def should_poll(self):
        """Return the polling state."""
        return self._should_poll
Пример #14
0
    async def async_update_data():
        """Fetch data from API endpoint.

        This is the place to pre-process the data to lookup tables
        so entities can quickly look up their data.

        This will ping Alexa API to identify all devices, bluetooth, and the last
        called device.

        This will add new devices and services when discovered. By default this
        runs every SCAN_INTERVAL seconds unless another method calls it. if
        websockets is connected, it will increase the delay 10-fold between updates.
        While throttled at MIN_TIME_BETWEEN_SCANS, care should be taken to
        reduce the number of runs to avoid flooding. Slow changing states
        should be checked here instead of in spawned components like
        media_player since this object is one per account.
        Each AlexaAPI call generally results in two webpage requests.
        """
        from alexapy import AlexaAPI

        email: Text = login_obj.email
        if email not in hass.data[DATA_ALEXAMEDIA]["accounts"]:
            return
        existing_serials = _existing_serials(hass, login_obj)
        existing_entities = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
            "entities"]["media_player"].values()
        auth_info = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get(
            "auth_info")
        new_devices = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
            "new_devices"]
        devices = {}
        bluetooth = {}
        preferences = {}
        dnd = {}
        raw_notifications = {}
        tasks = [
            AlexaAPI.get_devices(login_obj),
            AlexaAPI.get_bluetooth(login_obj),
            AlexaAPI.get_device_preferences(login_obj),
            AlexaAPI.get_dnd_state(login_obj),
            AlexaAPI.get_notifications(login_obj),
        ]
        if new_devices:
            tasks.append(AlexaAPI.get_authentication(login_obj))

        try:
            # Note: asyncio.TimeoutError and aiohttp.ClientError are already
            # handled by the data update coordinator.
            async with async_timeout.timeout(10):
                if new_devices:
                    (
                        devices,
                        bluetooth,
                        preferences,
                        dnd,
                        raw_notifications,
                        auth_info,
                    ) = await asyncio.gather(*tasks)
                else:
                    (
                        devices,
                        bluetooth,
                        preferences,
                        dnd,
                        raw_notifications,
                    ) = await asyncio.gather(*tasks)
                _LOGGER.debug(
                    "%s: Found %s devices, %s bluetooth",
                    hide_email(email),
                    len(devices) if devices is not None else "",
                    len(bluetooth.get("bluetoothStates", []))
                    if bluetooth is not None else "",
                )
                if (devices is None or bluetooth is None) and not (hass.data[
                        DATA_ALEXAMEDIA]["accounts"][email]["configurator"]):
                    raise AlexapyLoginError()
        except (AlexapyLoginError, RuntimeError, JSONDecodeError):
            _LOGGER.debug("%s: Alexa API disconnected; attempting to relogin",
                          hide_email(email))
            await login_obj.login_with_cookie()
            await test_login_status(hass, config_entry, login_obj, setup_alexa)
            return
        except BaseException as err:
            raise UpdateFailed(f"Error communicating with API: {err}")

        await process_notifications(login_obj, raw_notifications)
        # Process last_called data to fire events
        await update_last_called(login_obj)

        new_alexa_clients = []  # list of newly discovered device names
        exclude_filter = []
        include_filter = []

        for device in devices:
            serial = device["serialNumber"]
            dev_name = device["accountName"]
            if include and dev_name not in include:
                include_filter.append(dev_name)
                if "appDeviceList" in device:
                    for app in device["appDeviceList"]:
                        (hass.data[DATA_ALEXAMEDIA]["accounts"][email]
                         ["excluded"][app["serialNumber"]]) = device
                hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][
                    serial] = device
                continue
            elif exclude and dev_name in exclude:
                exclude_filter.append(dev_name)
                if "appDeviceList" in device:
                    for app in device["appDeviceList"]:
                        (hass.data[DATA_ALEXAMEDIA]["accounts"][email]
                         ["excluded"][app["serialNumber"]]) = device
                hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][
                    serial] = device
                continue

            if "bluetoothStates" in bluetooth:
                for b_state in bluetooth["bluetoothStates"]:
                    if serial == b_state["deviceSerialNumber"]:
                        device["bluetooth_state"] = b_state
                        break

            if "devicePreferences" in preferences:
                for dev in preferences["devicePreferences"]:
                    if dev["deviceSerialNumber"] == serial:
                        device["locale"] = dev["locale"]
                        device["timeZoneId"] = dev["timeZoneId"]
                        _LOGGER.debug(
                            "%s: Locale %s timezone %s",
                            dev_name,
                            device["locale"],
                            device["timeZoneId"],
                        )
                        break

            if "doNotDisturbDeviceStatusList" in dnd:
                for dev in dnd["doNotDisturbDeviceStatusList"]:
                    if dev["deviceSerialNumber"] == serial:
                        device["dnd"] = dev["enabled"]
                        _LOGGER.debug("%s: DND %s", dev_name, device["dnd"])
                        hass.data[DATA_ALEXAMEDIA]["accounts"][email][
                            "devices"]["switch"].setdefault(
                                serial, {"dnd": True})

                        break
            hass.data[DATA_ALEXAMEDIA]["accounts"][email][
                "auth_info"] = device["auth_info"] = auth_info
            hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
                "media_player"][serial] = device

            if serial not in existing_serials:
                new_alexa_clients.append(dev_name)
            elif serial in existing_entities:
                await hass.data[DATA_ALEXAMEDIA]["accounts"][email][
                    "entities"]["media_player"].get(serial).refresh(
                        device, no_api=True)
        _LOGGER.debug(
            "%s: Existing: %s New: %s;"
            " Filtered out by not being in include: %s "
            "or in exclude: %s",
            hide_email(email),
            list(existing_entities),
            new_alexa_clients,
            include_filter,
            exclude_filter,
        )

        if new_alexa_clients:
            cleaned_config = config.copy()
            cleaned_config.pop(CONF_PASSWORD, None)
            # CONF_PASSWORD contains sensitive info which is no longer needed
            for component in ALEXA_COMPONENTS:
                _LOGGER.debug("Loading %s", component)
                hass.async_add_job(
                    hass.config_entries.async_forward_entry_setup(
                        config_entry, component))

        hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False
Пример #15
0
class AlexaAlarmControlPanel(AlarmControlPanel):
    """Implementation of Alexa Media Player alarm control panel."""
    def __init__(self, login, hass):
        # pylint: disable=unexpected-keyword-arg
        """Initialize the Alexa device."""
        from alexapy import AlexaAPI
        # Class info
        self._login = login
        self.alexa_api = AlexaAPI(self, login)
        self.alexa_api_session = login.session
        self.account = hide_email(login.email)
        self.hass = hass

        # Guard info
        self._appliance_id = None
        self._guard_entity_id = None
        self._friendly_name = "Alexa Guard"
        self._state = None
        self._should_poll = False
        self._attrs = {}

        data = self.alexa_api.get_guard_details(self._login)
        try:
            guard_dict = (
                data['locationDetails']['locationDetails']['Default_Location']
                ['amazonBridgeDetails']['amazonBridgeDetails']
                ['LambdaBridge_AAA/OnGuardSmartHomeBridgeService']
                ['applianceDetails']['applianceDetails'])
        except KeyError:
            guard_dict = {}
        for key, value in guard_dict.items():
            if value['modelName'] == "REDROCK_GUARD_PANEL":
                self._appliance_id = value['applianceId']
                self._guard_entity_id = value['entityId']
                self._friendly_name += " " + self._appliance_id[-5:]
                _LOGGER.debug("%s: Discovered %s: %s %s", self.account,
                              self._friendly_name, self._appliance_id,
                              self._guard_entity_id)
        if not self._appliance_id:
            _LOGGER.debug("%s: No Alexa Guard entity found", self.account)
            return None
        # Register event handler on bus
        hass.bus.listen(('{}_{}'.format(ALEXA_DOMAIN,
                                        hide_email(login.email)))[0:32],
                        self._handle_event)
        self.refresh(no_throttle=True)

    def _handle_event(self, event):
        """Handle websocket events.

        Used instead of polling.
        """
        self.refresh()

    @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
    def refresh(self):
        """Update Guard state."""
        import json
        _LOGGER.debug("%s: Refreshing %s", self.account, self.name)
        state = None
        state_json = self.alexa_api.get_guard_state(self._login,
                                                    self._appliance_id)
        # _LOGGER.debug("%s: state_json %s", self.account, state_json)
        if (state_json and 'deviceStates' in state_json
                and state_json['deviceStates']):
            cap = state_json['deviceStates'][0]['capabilityStates']
            # _LOGGER.debug("%s: cap %s", self.account, cap)
            for item_json in cap:
                item = json.loads(item_json)
                # _LOGGER.debug("%s: item %s", self.account, item)
                if item['name'] == 'armState':
                    state = item['value']
                    # _LOGGER.debug("%s: state %s", self.account, state)
        elif state_json['errors']:
            _LOGGER.debug(
                "%s: Error refreshing alarm_control_panel %s: %s",
                self.account, self.name,
                json.dumps(state_json['errors']) if state_json else None)
        if state is None:
            return
        if state == "ARMED_AWAY":
            self._state = STATE_ALARM_ARMED_AWAY
        elif state == "ARMED_STAY":
            self._state = STATE_ALARM_DISARMED
        else:
            self._state = STATE_ALARM_DISARMED
        _LOGGER.debug("%s: Alarm State: %s", self.account, self.state)

    def alarm_disarm(self, code=None):
        # pylint: disable=unexpected-keyword-arg
        """Send disarm command.

        We use the arm_home state as Alexa does not have disarm state.
        """
        self.alarm_arm_home()
        self.schedule_update_ha_state()

    def alarm_arm_home(self, code=None):
        """Send arm home command."""
        self.alexa_api.set_guard_state(self._login, self._guard_entity_id,
                                       "ARMED_STAY")
        self.refresh(no_throttle=True)
        self.schedule_update_ha_state()

    def alarm_arm_away(self, code=None):
        """Send arm away command."""
        # pylint: disable=unexpected-keyword-arg
        self.alexa_api.set_guard_state(self._login, self._guard_entity_id,
                                       "ARMED_AWAY")
        self.refresh(no_throttle=True)
        self.schedule_update_ha_state()

    @property
    def unique_id(self):
        """Return the unique ID."""
        return self._guard_entity_id

    @property
    def name(self):
        """Return the name of the device."""
        return self._friendly_name

    @property
    def state(self):
        """Return the state of the device."""
        return self._state

    @property
    def device_state_attributes(self):
        """Return the state attributes."""
        return self._attrs

    @property
    def should_poll(self):
        """Return the polling state."""
        return self._should_poll or not (self.hass.data[DATA_ALEXAMEDIA][
            'accounts'][self._login.email]['websocket'])
Пример #16
0
    async def async_update_data():
        """Fetch data from API endpoint.

        This is the place to pre-process the data to lookup tables
        so entities can quickly look up their data.

        This will ping Alexa API to identify all devices, bluetooth, and the last
        called device.

        This will add new devices and services when discovered. By default this
        runs every SCAN_INTERVAL seconds unless another method calls it. if
        websockets is connected, it will increase the delay 10-fold between updates.
        While throttled at MIN_TIME_BETWEEN_SCANS, care should be taken to
        reduce the number of runs to avoid flooding. Slow changing states
        should be checked here instead of in spawned components like
        media_player since this object is one per account.
        Each AlexaAPI call generally results in two webpage requests.
        """
        email = config.get(CONF_EMAIL)
        login_obj = hass.data[DATA_ALEXAMEDIA]["accounts"][email]["login_obj"]
        if (email not in hass.data[DATA_ALEXAMEDIA]["accounts"]
                or not login_obj.status.get("login_successful")
                or login_obj.session.closed or login_obj.close_requested):
            return
        existing_serials = _existing_serials(hass, login_obj)
        existing_entities = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
            "entities"]["media_player"].values()
        auth_info = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get(
            "auth_info")
        new_devices = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
            "new_devices"]
        devices = {}
        bluetooth = {}
        preferences = {}
        dnd = {}
        raw_notifications = {}
        tasks = [
            AlexaAPI.get_devices(login_obj),
            AlexaAPI.get_bluetooth(login_obj),
            AlexaAPI.get_device_preferences(login_obj),
            AlexaAPI.get_dnd_state(login_obj),
        ]
        if new_devices:
            tasks.append(AlexaAPI.get_authentication(login_obj))

        try:
            # Note: asyncio.TimeoutError and aiohttp.ClientError are already
            # handled by the data update coordinator.
            async with async_timeout.timeout(30):
                if new_devices:
                    (
                        devices,
                        bluetooth,
                        preferences,
                        dnd,
                        auth_info,
                    ) = await asyncio.gather(*tasks)
                else:
                    (
                        devices,
                        bluetooth,
                        preferences,
                        dnd,
                    ) = await asyncio.gather(*tasks)
                _LOGGER.debug(
                    "%s: Found %s devices, %s bluetooth",
                    hide_email(email),
                    len(devices) if devices is not None else "",
                    len(bluetooth.get("bluetoothStates", []))
                    if bluetooth is not None else "",
                )
            await process_notifications(login_obj, raw_notifications)
            # Process last_called data to fire events
            await update_last_called(login_obj)
        except (AlexapyLoginError, JSONDecodeError):
            _LOGGER.debug(
                "%s: Alexa API disconnected; attempting to relogin : status %s",
                hide_email(email),
                login_obj.status,
            )
            if login_obj.status:
                hass.bus.async_fire(
                    "alexa_media_relogin_required",
                    event_data={
                        "email": hide_email(email),
                        "url": login_obj.url
                    },
                )
            return
        except BaseException as err:
            raise UpdateFailed(f"Error communicating with API: {err}")

        new_alexa_clients = []  # list of newly discovered device names
        exclude_filter = []
        include_filter = []

        for device in devices:
            serial = device["serialNumber"]
            dev_name = device["accountName"]
            if include and dev_name not in include:
                include_filter.append(dev_name)
                if "appDeviceList" in device:
                    for app in device["appDeviceList"]:
                        (hass.data[DATA_ALEXAMEDIA]["accounts"][email]
                         ["excluded"][app["serialNumber"]]) = device
                hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][
                    serial] = device
                continue
            if exclude and dev_name in exclude:
                exclude_filter.append(dev_name)
                if "appDeviceList" in device:
                    for app in device["appDeviceList"]:
                        (hass.data[DATA_ALEXAMEDIA]["accounts"][email]
                         ["excluded"][app["serialNumber"]]) = device
                hass.data[DATA_ALEXAMEDIA]["accounts"][email]["excluded"][
                    serial] = device
                continue

            if (dev_name not in include_filter and device.get("capabilities")
                    and not any(
                        x in device["capabilities"] for x in
                        ["MUSIC_SKILL", "TIMERS_AND_ALARMS", "REMINDERS"])):
                # skip devices without music or notification skill
                _LOGGER.debug("Excluding %s for lacking capability", dev_name)
                continue

            if "bluetoothStates" in bluetooth:
                for b_state in bluetooth["bluetoothStates"]:
                    if serial == b_state["deviceSerialNumber"]:
                        device["bluetooth_state"] = b_state
                        break

            if "devicePreferences" in preferences:
                for dev in preferences["devicePreferences"]:
                    if dev["deviceSerialNumber"] == serial:
                        device["locale"] = dev["locale"]
                        device["timeZoneId"] = dev["timeZoneId"]
                        _LOGGER.debug(
                            "%s: Locale %s timezone %s",
                            dev_name,
                            device["locale"],
                            device["timeZoneId"],
                        )
                        break

            if "doNotDisturbDeviceStatusList" in dnd:
                for dev in dnd["doNotDisturbDeviceStatusList"]:
                    if dev["deviceSerialNumber"] == serial:
                        device["dnd"] = dev["enabled"]
                        _LOGGER.debug("%s: DND %s", dev_name, device["dnd"])
                        hass.data[DATA_ALEXAMEDIA]["accounts"][email][
                            "devices"]["switch"].setdefault(
                                serial, {"dnd": True})

                        break
            hass.data[DATA_ALEXAMEDIA]["accounts"][email][
                "auth_info"] = device["auth_info"] = auth_info
            hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
                "media_player"][serial] = device

            if serial not in existing_serials:
                new_alexa_clients.append(dev_name)
            elif (serial in existing_serials and hass.data[DATA_ALEXAMEDIA]
                  ["accounts"][email]["entities"]["media_player"].get(serial)
                  and hass.data[DATA_ALEXAMEDIA]["accounts"][email]["entities"]
                  ["media_player"].get(serial).enabled):
                await hass.data[DATA_ALEXAMEDIA]["accounts"][email][
                    "entities"]["media_player"].get(serial).refresh(
                        device, skip_api=True)
        _LOGGER.debug(
            "%s: Existing: %s New: %s;"
            " Filtered out by not being in include: %s "
            "or in exclude: %s",
            hide_email(email),
            list(existing_entities),
            new_alexa_clients,
            include_filter,
            exclude_filter,
        )

        if new_alexa_clients:
            cleaned_config = config.copy()
            cleaned_config.pop(CONF_PASSWORD, None)
            # CONF_PASSWORD contains sensitive info which is no longer needed
            for component in ALEXA_COMPONENTS:
                entry_setup = len(hass.data[DATA_ALEXAMEDIA]["accounts"][email]
                                  ["entities"][component])
                if not entry_setup:
                    _LOGGER.debug("Loading config entry for %s", component)
                    hass.async_add_job(
                        hass.config_entries.async_forward_entry_setup(
                            config_entry, component))
                else:
                    _LOGGER.debug("Loading %s", component)
                    hass.async_create_task(
                        async_load_platform(
                            hass,
                            component,
                            DOMAIN,
                            {
                                CONF_NAME: DOMAIN,
                                "config": cleaned_config
                            },
                            cleaned_config,
                        ))

        hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False
        await login_obj.save_cookiefile()
        if login_obj.access_token:
            hass.config_entries.async_update_entry(
                config_entry,
                data={
                    **config_entry.data,
                    CONF_OAUTH: {
                        "access_token": login_obj.access_token,
                        "refresh_token": login_obj.refresh_token,
                        "expires_in": login_obj.expires_in,
                    },
                },
            )