Esempio n. 1
0
    async def ws_connect() -> WebsocketEchoClient:
        """Open WebSocket connection.

        This will only attempt one login before failing.
        """
        websocket: Optional[WebsocketEchoClient] = None
        try:
            if login_obj.session.closed:
                _LOGGER.debug(
                    "%s: Websocket creation aborted. Session is closed.",
                    hide_email(email),
                )
                return
            websocket = WebsocketEchoClient(
                login_obj,
                ws_handler,
                ws_open_handler,
                ws_close_handler,
                ws_error_handler,
            )
            _LOGGER.debug("%s: Websocket created: %s", hide_email(email),
                          websocket)
            await websocket.async_run()
        except BaseException as exception_:  # pylint: disable=broad-except
            _LOGGER.debug("%s: Websocket creation failed: %s",
                          hide_email(email), exception_)
            return
        return websocket
Esempio n. 2
0
    def ws_connect():
        """Open WebSocket connection.

        This will only attempt one login before failing.
        """
        from alexapy import WebsocketEchoClient
        try:
            websocket = WebsocketEchoClient(login_obj, ws_handler,
                                            ws_close_handler, ws_error_handler)
            _LOGGER.debug("%s: Websocket created: %s", hide_email(email),
                          websocket)
        except BaseException as exception_:
            _LOGGER.debug("%s: Websocket failed: %s falling back to polling",
                          hide_email(email), exception_)
            websocket = None
        return websocket
Esempio n. 3
0
def setup_alexa(hass, config, login_obj):
    """Set up a alexa api based on host parameter."""
    @util.Throttle(MIN_TIME_BETWEEN_SCANS, MIN_TIME_BETWEEN_FORCED_SCANS)
    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)

    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

    def last_call_handler(call):
        """Handle last call service request.

        Args:
        call.ATTR_EMAIL: List of case-sensitive Alexa email addresses. If None
                         all accounts are updated.
        """
        requested_emails = call.data.get(ATTR_EMAIL)
        _LOGGER.debug("Service update_last_called for: %s", requested_emails)
        for email, account_dict in (
                hass.data[DATA_ALEXAMEDIA]['accounts'].items()):
            if requested_emails and email not in requested_emails:
                continue
            login_obj = account_dict['login_obj']
            update_last_called(login_obj)

    def ws_handler(message_obj):
        """Handle websocket messages.

        This allows push notifications from Alexa to update last_called
        and media state.
        """
        command = (message_obj.json_payload['command']
                   if isinstance(message_obj.json_payload, dict)
                   and 'command' in message_obj.json_payload else None)
        json_payload = (message_obj.json_payload['payload']
                        if isinstance(message_obj.json_payload, dict)
                        and 'payload' in message_obj.json_payload else None)
        if command and json_payload:
            _LOGGER.debug("%s: Received websocket command: %s : %s",
                          hide_email(email), command, json_payload)
            if command == 'PUSH_ACTIVITY':
                #  Last_Alexa Updated
                last_called = {
                    'serialNumber':
                    (json_payload['key']['entryId']).split('#')[2],
                    'timestamp': json_payload['timestamp']
                }
                update_last_called(login_obj, last_called)
            elif command == 'PUSH_AUDIO_PLAYER_STATE':
                # Player update
                _LOGGER.debug("Updating media_player: %s", json_payload)
                hass.bus.fire(('{}_{}'.format(DOMAIN,
                                              hide_email(email)))[0:32],
                              {'player_state': json_payload})
            elif command == 'PUSH_VOLUME_CHANGE':
                # Player volume update
                _LOGGER.debug("Updating media_player volume: %s", json_payload)
                hass.bus.fire(('{}_{}'.format(DOMAIN,
                                              hide_email(email)))[0:32],
                              {'player_state': json_payload})

    include = config.get(CONF_INCLUDE_DEVICES)
    exclude = config.get(CONF_EXCLUDE_DEVICES)
    scan_interval = config.get(CONF_SCAN_INTERVAL)
    email = login_obj.email
    from alexapy import WebsocketEchoClient
    try:
        websocket = WebsocketEchoClient(login_obj, ws_handler)
        _LOGGER.debug("%s: Websocket created: %s", hide_email(email),
                      websocket)
    except BaseException as exception_:
        _LOGGER.exception("%s: Websocket failed: %s", hide_email(email),
                          exception_)
        websocket = None
    (hass.data[DOMAIN]['accounts'][email]['websocket']) = websocket
    (hass.data[DOMAIN]['accounts'][email]['login_obj']) = login_obj
    (hass.data[DOMAIN]['accounts'][email]['devices']) = {'media_player': {}}
    (hass.data[DOMAIN]['accounts'][email]['entities']) = {'media_player': {}}
    update_devices()
    track_time_interval(hass, lambda now: update_devices(), scan_interval)
    hass.services.register(DOMAIN,
                           SERVICE_UPDATE_LAST_CALLED,
                           last_call_handler,
                           schema=LAST_CALL_UPDATE_SCHEMA)

    # Clear configurator. We delay till here to avoid leaving a modal orphan
    for config_id in hass.data[DOMAIN]['accounts'][email]['config']:
        configurator = hass.components.configurator
        configurator.async_request_done(config_id)
    hass.data[DOMAIN]['accounts'][email]['config'] = []
    return True