Exemple #1
0
    async def fetching_data(self, *_):
        """Get the latest data from yr.no."""
        import xmltodict

        def try_again(err: str):
            """Retry in 15 to 20 minutes."""
            minutes = 15 + randrange(6)
            _LOGGER.error("Retrying in %i minutes: %s", minutes, err)
            async_call_later(self.hass, minutes*60, self.fetching_data)
        try:
            websession = async_get_clientsession(self.hass)
            with async_timeout.timeout(10, loop=self.hass.loop):
                resp = await websession.get(
                    self._url, params=self._urlparams)
            if resp.status != 200:
                try_again('{} returned {}'.format(resp.url, resp.status))
                return
            text = await resp.text()

        except (asyncio.TimeoutError, aiohttp.ClientError) as err:
            try_again(err)
            return

        try:
            self.data = xmltodict.parse(text)['weatherdata']
        except (ExpatError, IndexError) as err:
            try_again(err)
            return

        await self.updating_devices()
        async_call_later(self.hass, 60*60, self.fetching_data)
    async def _fetch_data(self, *_):
        """Get the latest data from met.no."""
        if not await self._weather_data.fetching_data():
            # Retry in 15 to 20 minutes.
            minutes = 15 + randrange(6)
            _LOGGER.error("Retrying in %i minutes", minutes)
            async_call_later(self.hass, minutes*60, self._fetch_data)
            return

        async_call_later(self.hass, 60*60, self._fetch_data)
        await self._update()
    async def _attempt_connect(self):
        """Attempt to connect to the socket (retrying later on fail)."""
        from aioambient.errors import WebsocketError

        try:
            await self.client.websocket.connect()
        except WebsocketError as err:
            _LOGGER.error("Error with the websocket connection: %s", err)
            self._ws_reconnect_delay = min(
                2 * self._ws_reconnect_delay, 480)
            async_call_later(
                self._hass, self._ws_reconnect_delay, self.ws_connect)
        def state_message_received(msg):
            """Handle a new received MQTT state message."""
            payload = msg.payload
            value_template = self._config.get(CONF_VALUE_TEMPLATE)
            if value_template is not None:
                payload = value_template.async_render_with_possible_json_value(
                    payload, variables={'entity_id': self.entity_id})
            if payload == self._config.get(CONF_PAYLOAD_ON):
                self._state = True
            elif payload == self._config.get(CONF_PAYLOAD_OFF):
                self._state = False
            else:  # Payload is not for this entity
                _LOGGER.warning('No matching payload found'
                                ' for entity: %s with state_topic: %s',
                                self._config.get(CONF_NAME),
                                self._config.get(CONF_STATE_TOPIC))
                return

            if self._delay_listener is not None:
                self._delay_listener()
                self._delay_listener = None

            off_delay = self._config.get(CONF_OFF_DELAY)
            if (self._state and off_delay is not None):
                self._delay_listener = evt.async_call_later(
                    self.hass, off_delay, off_delay_listener)

            self.async_write_ha_state()
    def parse_data(self, data, raw_data):
        """Parse data sent by gateway.

        Polling (proto v1, firmware version 1.4.1_159.0143)

        >> { "cmd":"read","sid":"158..."}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'read_ack', 'data': '{"voltage":3005}'}

        Multicast messages (proto v1, firmware version 1.4.1_159.0143)

        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'report', 'data': '{"status":"motion"}'}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'report', 'data': '{"no_motion":"120"}'}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'report', 'data': '{"no_motion":"180"}'}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
           'cmd': 'report', 'data': '{"no_motion":"300"}'}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'heartbeat', 'data': '{"voltage":3005}'}

        """
        if raw_data['cmd'] == 'heartbeat':
            _LOGGER.debug(
                'Skipping heartbeat of the motion sensor. '
                'It can introduce an incorrect state because of a firmware '
                'bug (https://github.com/home-assistant/home-assistant/pull/'
                '11631#issuecomment-357507744).')
            return

        if NO_MOTION in data:
            self._no_motion_since = data[NO_MOTION]
            self._state = False
            return True

        value = data.get(self._data_key)
        if value is None:
            return False

        if value == MOTION:
            if self._data_key == 'motion_status':
                if self._unsub_set_no_motion:
                    self._unsub_set_no_motion()
                self._unsub_set_no_motion = async_call_later(
                    self._hass,
                    120,
                    self._async_set_no_motion
                )

            if self.entity_id is not None:
                self._hass.bus.fire('xiaomi_aqara.motion', {
                    'entity_id': self.entity_id
                })

            self._no_motion_since = 0
            if self._state:
                return False
            self._state = True
            return True
 def on_connect():
     """Define a handler to fire when the websocket is connected."""
     _LOGGER.info('Connected to websocket')
     _LOGGER.debug('Watchdog starting')
     if self._watchdog_listener:
         self._watchdog_listener()
     self._watchdog_listener = async_call_later(
         self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)
Exemple #7
0
    def parse_data(self, data, raw_data):
        """Parse data sent by gateway."""
        value = data.get(VERIFIED_WRONG_KEY)
        if value is not None:
            self._verified_wrong_times = int(value)
            return True

        for key in (FINGER_KEY, PASSWORD_KEY, CARD_KEY):
            value = data.get(key)
            if value is not None:
                self._changed_by = int(value)
                self._verified_wrong_times = 0
                self._state = STATE_UNLOCKED
                async_call_later(self.hass, UNLOCK_MAINTAIN_TIME,
                                 self.clear_unlock_state)
                return True

        return False
        def on_data(data):
            """Define a handler to fire when the data is received."""
            mac_address = data['macAddress']
            if data != self.stations[mac_address][ATTR_LAST_DATA]:
                _LOGGER.debug('New data received: %s', data)
                self.stations[mac_address][ATTR_LAST_DATA] = data
                async_dispatcher_send(self._hass, TOPIC_UPDATE)

            _LOGGER.debug('Resetting watchdog')
            self._watchdog_listener()
            self._watchdog_listener = async_call_later(
                self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)
Exemple #9
0
    def get_segment(self, sequence: int = None) -> Any:
        """Retrieve a specific segment, or the whole list."""
        self.idle = False
        # Reset idle timeout
        if self._unsub is not None:
            self._unsub()
        self._unsub = async_call_later(self._stream.hass, 300, self._timeout)

        if not sequence:
            return self._segments

        for segment in self._segments:
            if segment.sequence == sequence:
                return segment
        return None
Exemple #10
0
    def async_delay_save(self, data_func: Callable[[], Dict],
                         delay: Optional[int] = None):
        """Save data with an optional delay."""
        self._data = {
            'version': self.version,
            'key': self.key,
            'data_func': data_func,
        }

        self._async_cleanup_delay_listener()

        self._unsub_delay_listener = async_call_later(
            self.hass, delay, self._async_callback_delayed_write)

        self._async_ensure_stop_listener()
Exemple #11
0
def test_async_call_later(hass):
    """Test calling an action later."""
    def action(): pass
    now = datetime(2017, 12, 19, 15, 40, 0, tzinfo=dt_util.UTC)

    with patch('homeassistant.helpers.event'
               '.async_track_point_in_utc_time') as mock, \
            patch('homeassistant.util.dt.utcnow', return_value=now):
        remove = async_call_later(hass, 3, action)

    assert len(mock.mock_calls) == 1
    p_hass, p_action, p_point = mock.mock_calls[0][1]
    assert p_hass is hass
    assert p_action is action
    assert p_point == now + timedelta(seconds=3)
    assert remove is mock()
Exemple #12
0
 async def update(self, *args):
     """Periodically poll the servers for current state."""
     try:
         if not await self._hass.async_add_executor_job(
                 self._client.update):
             _LOGGER.warning('Failed request')
             return
         dev_ids = {dev.device_id for dev in self._client.devices}
         new_devices = dev_ids - self._known_devices
         # just await each discover as `gather` use up all HTTPAdapter pools
         for d_id in new_devices:
             await self._discover(d_id)
         self._known_devices |= new_devices
         async_dispatcher_send(self._hass, SIGNAL_UPDATE_ENTITY)
     finally:
         self._hass.data[INTERVAL_TRACKER] = async_call_later(
             self._hass, self._interval, self.update)
    def parse_data(self, data, raw_data):
        """Parse data sent by gateway."""
        if raw_data['cmd'] == 'heartbeat':
            _LOGGER.debug(
                'Skipping heartbeat of the motion sensor. '
                'It can introduce an incorrect state because of a firmware '
                'bug (https://github.com/home-assistant/home-assistant/pull/'
                '11631#issuecomment-357507744).')
            return

        self._should_poll = False
        if NO_MOTION in data:  # handle push from the hub
            self._no_motion_since = data[NO_MOTION]
            self._state = False
            return True

        value = data.get(self._data_key)
        if value is None:
            return False

        if value == MOTION:
            if self._data_key == 'motion_status':
                if self._unsub_set_no_motion:
                    self._unsub_set_no_motion()
                self._unsub_set_no_motion = async_call_later(
                    self._hass,
                    180,
                    self._async_set_no_motion
                )
            else:
                self._should_poll = True
                if self.entity_id is not None:
                    self._hass.bus.fire('motion', {
                        'entity_id': self.entity_id
                    })

            self._no_motion_since = 0
            if self._state:
                return False
            self._state = True
            return True
        if value == NO_MOTION:
            if not self._state:
                return False
            self._state = False
            return True
Exemple #14
0
    def put(self, segment: Segment) -> None:
        """Store output."""
        # Start idle timeout when we start recieving data
        if self._unsub is None:
            self._unsub = async_call_later(
                self._stream.hass, self.timeout, self._timeout)

        if segment is None:
            self._event.set()
            # Cleanup provider
            if self._unsub is not None:
                self._unsub()
            self.cleanup()
            return

        self._segments.append(segment)
        self._event.set()
        self._event.clear()
    def _handle_event(self, event):
        """Domain specific event handler."""
        command = event['command']
        if command == 'on':
            self._state = True
        elif command == 'off':
            self._state = False

        if (self._state and self._off_delay is not None):
            def off_delay_listener(now):
                """Switch device off after a delay."""
                self._delay_listener = None
                self._state = False
                self.async_schedule_update_ha_state()

            if self._delay_listener is not None:
                self._delay_listener()
            self._delay_listener = evt.async_call_later(
                self.hass, self._off_delay, off_delay_listener)
    async def async_save(self, data: Dict, *, delay: Optional[int] = None):
        """Save data with an optional delay."""
        self._data = {
            'version': self.version,
            'key': self.key,
            'data': data,
        }

        self._async_cleanup_delay_listener()

        if delay is None:
            self._async_cleanup_stop_listener()
            await self._async_handle_write_data()
            return

        self._unsub_delay_listener = async_call_later(
            self.hass, delay, self._async_callback_delayed_write)

        self._async_ensure_stop_listener()
Exemple #17
0
        def state_message_received(_topic, payload, _qos):
            """Handle a new received MQTT state message."""
            if self._template is not None:
                payload = self._template.async_render_with_possible_json_value(
                    payload)
            if payload == self._payload_on:
                self._state = True
            elif payload == self._payload_off:
                self._state = False
            else:  # Payload is not for this entity
                _LOGGER.warning('No matching payload found'
                                ' for entity: %s with state_topic: %s',
                                self._name, self._state_topic)
                return

            if self._delay_listener is not None:
                self._delay_listener()
                self._delay_listener = None

            if (self._state and self._off_delay is not None):
                self._delay_listener = evt.async_call_later(
                    self.hass, self._off_delay, off_delay_listener)

            self.async_schedule_update_ha_state()
Exemple #18
0
        return services

    try:
        services = await account_link.async_fetch_available_services(
            hass.data[DOMAIN])
    except (aiohttp.ClientError, asyncio.TimeoutError):
        return []

    hass.data[DATA_SERVICES] = services

    @callback
    def clear_services(_now):
        """Clear services cache."""
        hass.data.pop(DATA_SERVICES, None)

    event.async_call_later(hass, CACHE_TIMEOUT, clear_services)

    return services


class CloudOAuth2Implementation(
        config_entry_oauth2_flow.AbstractOAuth2Implementation):
    """Cloud implementation of the OAuth2 flow."""
    def __init__(self, hass: HomeAssistant, service: str) -> None:
        """Initialize cloud OAuth2 implementation."""
        self.hass = hass
        self.service = service

    @property
    def name(self) -> str:
        """Name of the implementation."""
Exemple #19
0
 def _state_updated(entity_id, old_state, new_state):
     """Schedule an update."""
     async_call_later(self.hass, 4, _refresh_associated)
     self.async_schedule_update_ha_state()
Exemple #20
0
 def _update_device_soon(self):
     if self._update_device_cb is not None:
         self._update_device_cb()  # unsubscribe
     self._update_device_cb = async_call_later(self._hass, 1,
                                               self._update_device)
    def parse_data(self, data, raw_data):
        """Parse data sent by gateway.

        Polling (proto v1, firmware version 1.4.1_159.0143)

        >> { "cmd":"read","sid":"158..."}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'read_ack', 'data': '{"voltage":3005}'}

        Multicast messages (proto v1, firmware version 1.4.1_159.0143)

        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'report', 'data': '{"status":"motion"}'}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'report', 'data': '{"no_motion":"120"}'}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'report', 'data': '{"no_motion":"180"}'}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
           'cmd': 'report', 'data': '{"no_motion":"300"}'}
        << {'model': 'motion', 'sid': '158...', 'short_id': 26331,
            'cmd': 'heartbeat', 'data': '{"voltage":3005}'}

        """
        _LOGGER.debug("Motion Sensor Data %s", data)
        if raw_data["cmd"] == "heartbeat":
            _LOGGER.debug(
                "Skipping heartbeat of the motion sensor. "
                "It can introduce an incorrect state because of a firmware "
                "bug (https://github.com/home-assistant/home-assistant/pull/"
                "11631#issuecomment-357507744)."
            )
            return

        if NO_MOTION in data:
            self._no_motion_since = data[NO_MOTION]
            self._state = False
            return True

        value = data.get(self._data_key)
        if value is None:
            return False

        if value == MOTION:
            if self._data_key == "motion_status":
                if self._unsub_set_no_motion:
                    self._unsub_set_no_motion()
                self._unsub_set_no_motion = async_call_later(
                    self._hass,
                    MOTION_SENSOR_NO_MOTION_TIMEOUT,
                    self._async_set_no_motion,
                )

            if self.entity_id is not None:
                self._hass.bus.fire(
                    "xiaomi_aqara.motion", {"entity_id": self.entity_id}
                )

            self._no_motion_since = 0
            if self._state:
                return False
            self._state = True
            return True
Exemple #22
0
async def async_setup(hass, config):
    """Set up ZHA from config."""

    if DOMAIN not in config:
        return True

    try:
        zha_gateway = hass.data["zha"]["zha_gateway"]
    except KeyError:
        return False

    builder = TopologyBuilder(hass, zha_gateway)
    hass.data[DOMAIN] = {ATTR_TOPO: builder}
    output_dir = os.path.join(hass.config.config_dir, CONFIG_OUTPUT_DIR_NAME)
    hass.data[DOMAIN][ATTR_OUTPUT_DIR] = output_dir

    def mkdir(dir):
        try:
            os.mkdir(dir)
            return True
        except OSError as exc:
            LOGGER.error("Couldn't create '%s' dir: %s", dir, exc)
            return False

    if not os.path.isdir(output_dir):
        if not await hass.async_add_executor_job(mkdir, output_dir):
            return False

    async def setup_scanner(_now):
        async_track_time_interval(hass, builder.time_tracker, AWAKE_INTERVAL)
        await builder.time_tracker()

    async_call_later(hass, CONFIG_INITIAL_SCAN_DELAY, setup_scanner)

    async def scan_now_handler(service):
        """Scan topology right now."""
        await builder.preempt_build()

    hass.services.async_register(
        DOMAIN,
        SERVICE_SCAN_NOW,
        scan_now_handler,
        schema=SERVICE_SCHEMAS[SERVICE_SCAN_NOW],
    )

    @websocket_api.require_admin
    @websocket_api.async_response
    @websocket_api.websocket_command(
        {vol.Required(ATTR_TYPE): f"{DOMAIN}/devices"})
    async def websocket_get_devices(hass, connection, msg):
        """Get ZHA Map devices."""

        response = {
            "time": builder.timestamp,
            "devices": [nei.json() for nei in builder.current.values()],
        }
        connection.send_result(msg["id"], response)

    websocket_api.async_register_command(hass, websocket_get_devices)

    return True
Exemple #23
0
 def schedule_import(_):
     """Schedule delayed import after HA is fully started."""
     async_call_later(hass, 10, do_import)
Exemple #24
0
 def try_again(err: str):
     """Retry in 15 to 20 minutes."""
     minutes = 15 + randrange(6)
     _LOGGER.error("Retrying in %i minutes: %s", minutes, err)
     async_call_later(self.hass, minutes * 60, self.fetching_data)
Exemple #25
0
async def hacs_startup():
    """HACS startup tasks."""
    hacs = get_hacs()
    if hacs.configuration.debug:
        try:
            await hacs.hass.services.async_call("logger", "set_level",
                                                {"hacs": "debug"})
            await hacs.hass.services.async_call("logger", "set_level",
                                                {"queueman": "debug"})
            await hacs.hass.services.async_call("logger", "set_level",
                                                {"AioGitHub": "debug"})
        except ServiceNotFound:
            hacs.logger.error(
                "Could not set logging level to debug, logger is not enabled")

    lovelace_info = await system_health_info(hacs.hass)
    hacs.logger.debug(f"Configuration type: {hacs.configuration.config_type}")
    hacs.version = VERSION
    hacs.logger.info(STARTUP)
    hacs.system.config_path = hacs.hass.config.path()
    hacs.system.ha_version = HAVERSION

    hacs.system.lovelace_mode = lovelace_info.get("mode", "yaml")
    hacs.system.disabled = False
    hacs.github = GitHub(hacs.configuration.token,
                         async_create_clientsession(hacs.hass))
    hacs.data = HacsData()

    can_update = await get_fetch_updates_for(hacs.github)
    if can_update == 0:
        hacs.logger.info(
            "HACS is ratelimited, repository updates will resume in 1h.")
    else:
        hacs.logger.debug(f"Can update {can_update} repositories")

    # Check HACS Constrains
    if not await hacs.hass.async_add_executor_job(check_constrains):
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Set up frontend
    await setup_frontend()

    if not await hacs.hass.async_add_executor_job(internet_connectivity_check):
        hacs.logger.critical("No network connectivity")
        return False

    # Load HACS
    if not await load_hacs_repository():
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Restore from storefiles
    if not await hacs.data.restore():
        hacs_repo = hacs.get_by_name("hacs/integration")
        hacs_repo.pending_restart = True
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Add aditional categories
    hacs.common.categories = ELEMENT_TYPES
    if hacs.configuration.appdaemon:
        hacs.common.categories.append("appdaemon")
    if hacs.configuration.netdaemon:
        hacs.common.categories.append("netdaemon")

    # Setup startup tasks
    if hacs.configuration.config_type == "yaml":
        hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START,
                                        hacs.startup_tasks())
    else:
        async_call_later(hacs.hass, 5, hacs.startup_tasks())

    # Show the configuration
    hacs.configuration.print()

    # Set up sensor
    await hacs.hass.async_add_executor_job(add_sensor)

    # Mischief managed!
    return True
    async def async_refresh_sensor(self, sessions):
        """Set instance object and trigger an entity state update."""
        _LOGGER.debug("Refreshing sensor [%s]", self.unique_id)

        self.sessions = sessions
        update_failed = False

        @callback
        def update_plex(_):
            async_dispatcher_send(
                self.hass,
                PLEX_UPDATE_PLATFORMS_SIGNAL.format(
                    self._server.machine_identifier),
            )

        now_playing = []
        for sess in self.sessions:
            if sess.TYPE == "photo":
                _LOGGER.debug("Photo session detected, skipping: %s", sess)
                continue
            if not sess.usernames:
                _LOGGER.debug(
                    "Session temporarily incomplete, will try again: %s", sess)
                update_failed = True
                continue
            user = sess.usernames[0]
            device = sess.players[0].title
            now_playing_user = f"{user} - {device}"
            now_playing_title = ""

            if sess.TYPE == "episode":
                # example:
                # "Supernatural (2005) - s01e13 - Route 666"

                def sync_io_attributes(session):
                    year = None
                    try:
                        year = session.show().year
                    except TypeError:
                        pass
                    return (year, session.seasonEpisode)

                year, season_episode = await self.hass.async_add_executor_job(
                    sync_io_attributes, sess)
                season_title = sess.grandparentTitle
                if year is not None:
                    season_title += f" ({year!s})"
                episode_title = sess.title
                now_playing_title = (
                    f"{season_title} - {season_episode} - {episode_title}")
            elif sess.TYPE == "track":
                # example:
                # "Billy Talent - Afraid of Heights - Afraid of Heights"
                track_artist = sess.grandparentTitle
                track_album = sess.parentTitle
                track_title = sess.title
                now_playing_title = f"{track_artist} - {track_album} - {track_title}"
            elif sess.TYPE == "movie":
                # example:
                # "picture_of_last_summer_camp (2015)"
                # "The Incredible Hulk (2008)"
                now_playing_title = sess.title
                year = await self.hass.async_add_executor_job(
                    getattr, sess, "year")
                if year is not None:
                    now_playing_title += f" ({year})"
            else:
                now_playing_title = sess.title

            now_playing.append((now_playing_user, now_playing_title))
        self._state = len(self.sessions)
        self._now_playing = now_playing

        self.async_write_ha_state()

        if update_failed:
            async_call_later(self.hass, 5, update_plex)
Exemple #27
0
 def awake(self) -> None:
     """Keep the idle time alive by resetting the timeout."""
     self.idle = False
     # Reset idle timeout
     self.clear()
     self._unsub = async_call_later(self._hass, self._timeout, self.fire)
Exemple #28
0
 def start(self) -> None:
     """Start the idle timer if not already started."""
     self.idle = False
     if self._unsub is None:
         self._unsub = async_call_later(self._hass, self._timeout, self.fire)
Exemple #29
0
async def async_hacs_startup():
    """HACS startup tasks."""
    hacs = get_hacs()
    hacs.hass.data[DOMAIN] = hacs

    try:
        lovelace_info = await system_health_info(hacs.hass)
    except TypeError:
        # If this happens, the users YAML is not valid, we assume YAML mode
        lovelace_info = {"mode": "yaml"}
    hacs.log.debug(f"Configuration type: {hacs.configuration.config_type}")
    hacs.version = VERSION
    hacs.log.info(STARTUP)
    hacs.core.config_path = hacs.hass.config.path()
    hacs.system.ha_version = HAVERSION

    # Setup websocket API
    await async_setup_hacs_websockt_api()

    # Set up frontend
    await async_setup_frontend()

    # Clear old storage files
    await async_clear_storage()

    hacs.system.lovelace_mode = lovelace_info.get("mode", "yaml")
    hacs.system.disabled = False
    hacs.github = GitHub(
        hacs.configuration.token, async_create_clientsession(hacs.hass)
    )
    hacs.data = HacsData()

    can_update = await get_fetch_updates_for(hacs.github)
    if can_update is None:
        hacs.log.critical("Your GitHub token is not valid")
        return False

    if can_update != 0:
        hacs.log.debug(f"Can update {can_update} repositories")
    else:
        hacs.log.info(
            "HACS is ratelimited, repository updates will resume when the limit is cleared, this can take up to 1 hour"
        )
        return False

    # Check HACS Constrains
    if not await hacs.hass.async_add_executor_job(check_constrains):
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
        return False

    # Load HACS
    if not await async_load_hacs_repository():
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
        return False

    # Restore from storefiles
    if not await hacs.data.restore():
        hacs_repo = hacs.get_by_name("hacs/integration")
        hacs_repo.pending_restart = True
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass, hacs.configuration.config_entry)
        return False

    # Setup startup tasks
    if hacs.status.new:
        async_call_later(hacs.hass, 5, hacs.startup_tasks)
    else:
        hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, hacs.startup_tasks)

    # Set up sensor
    await async_add_sensor()

    # Mischief managed!
    await hacs.async_set_stage(HacsStage.WAITING)
    hacs.log.info(
        "Setup complete, waiting for Home Assistant before startup tasks starts"
    )
    return True
Exemple #30
0
    async def _attempt_connect(self, callback_dataload_complete):
        """Attempt to connect to the socket (retrying later on fail)."""
        def on_data(data):
            """Define a handler to fire when the data is received."""

            msg_type = data.WhichOneof('msg')

            if (msg_type == "vepUpdate"):  #VEPUpdate
                LOGGER.debug("vepUpdate")
                return

            if (msg_type == "vepUpdates"):  #VEPUpdatesByVIN

                self._process_vep_updates(data)

                sequence_number = data.vepUpdates.sequence_number
                LOGGER.debug(f"vepUpdates Sequence: {sequence_number}")
                ack_command = client_pb2.ClientMessage()
                ack_command.acknowledge_vep_updates_by_vin.sequence_number = sequence_number
                return ack_command

            if (msg_type == "debugMessage"):  #DebugMessage

                if data.debugMessage:
                    LOGGER.debug(
                        f"debugMessage - Data: {data.debugMessage.message}")

                return

            if (msg_type == "service_status_update"):
                LOGGER.debug(
                    f"service_status_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}"
                )
                return

            if (msg_type == "user_data_update"):
                LOGGER.debug(
                    f"user_data_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}"
                )
                return

            if (msg_type == "user_vehicle_auth_changed_update"):
                LOGGER.debug(
                    f"user_vehicle_auth_changed_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}"
                )
                return

            if (msg_type == "user_picture_update"):
                LOGGER.debug(
                    f"user_picture_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}"
                )
                return

            if (msg_type == "user_pin_update"):
                LOGGER.debug(
                    f"user_pin_update - Data: {MessageToJson(data, preserving_proto_field_name=True)}"
                )
                return

            if (msg_type == "vehicle_updated"):
                LOGGER.debug(
                    f"vehicle_updated - Data: {MessageToJson(data, preserving_proto_field_name=True)}"
                )
                return

            if (msg_type == "preferred_dealer_change"):
                LOGGER.debug(
                    f"preferred_dealer_change - Data: {MessageToJson(data, preserving_proto_field_name=True)}"
                )
                return

            if (msg_type == "apptwin_command_status_updates_by_vin"):
                LOGGER.debug(
                    f"apptwin_command_status_updates_by_vin - Data: {MessageToJson(data, preserving_proto_field_name=True)}"
                )
                sequence_number = data.apptwin_command_status_updates_by_vin.sequence_number
                LOGGER.debug("apptwin_command_status_updates_by_vin: %s",
                             sequence_number)
                ack_command = client_pb2.ClientMessage()
                ack_command.acknowledge_apptwin_command_status_update_by_vin.sequence_number = sequence_number
                return ack_command

            if (msg_type == "apptwin_pending_command_request"):
                #LOGGER.debug(f"apptwin_pending_command_request - Data: {MessageToJson(data, preserving_proto_field_name=True)}")
                return

            if (msg_type == "assigned_vehicles"):
                LOGGER.debug("assigned_vehicles")

                self._process_assigned_vehicles(data)

                return

            LOGGER.debug(f"Message Type not implemented - {msg_type}")

        try:
            self._on_dataload_complete = callback_dataload_complete
            await self.websocket.async_connect(on_data)
        except (WebsocketError) as err:
            LOGGER.error("Error with the websocket connection: %s", err)
            self._ws_reconnect_delay = self._ws_reconnect_delay
            async_call_later(self._hass, self._ws_reconnect_delay,
                             self.websocket.async_connect(on_data))
async def hacs_startup(hacs):
    """HACS startup tasks."""
    if hacs.configuration.debug:
        try:
            await hacs.hass.services.async_call("logger", "set_level",
                                                {"hacs": "debug"})
        except ServiceNotFound:
            hacs.logger.error(
                "Could not set logging level to debug, logger is not enabled")

    lovelace_info = await system_health_info(hacs.hass)
    hacs.logger.debug(f"Configuration type: {hacs.configuration.config_type}")
    hacs.version = VERSION
    hacs.logger.info(STARTUP)
    hacs.system.config_path = hacs.hass.config.path()
    hacs.system.ha_version = HAVERSION

    hacs.system.lovelace_mode = lovelace_info.get("mode", "yaml")
    hacs.system.disabled = False
    hacs.github = AIOGitHub(hacs.configuration.token,
                            async_create_clientsession(hacs.hass))
    hacs.data = HacsData()

    # Check HACS Constrains
    if not await hacs.hass.async_add_executor_job(check_constans, hacs):
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Set up frontend
    await setup_frontend(hacs)

    # Set up sensor
    await hacs.hass.async_add_executor_job(add_sensor, hacs)

    # Load HACS
    if not await load_hacs_repository(hacs):
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Restore from storefiles
    if not await hacs.data.restore():
        hacs_repo = hacs().get_by_name("hacs/integration")
        hacs_repo.pending_restart = True
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Add aditional categories
    hacs.common.categories = ELEMENT_TYPES
    if hacs.configuration.appdaemon:
        hacs.common.categories.append("appdaemon")
    if hacs.configuration.python_script:
        hacs.configuration.python_script = False
        if hacs.configuration.config_type == "yaml":
            hacs.logger.warning(
                "Configuration option 'python_script' is deprecated and you should remove it from your configuration, HACS will know if you use 'python_script' in your Home Assistant configuration, this option will be removed in a future release."
            )
    if hacs.configuration.theme:
        hacs.configuration.theme = False
        if hacs.configuration.config_type == "yaml":
            hacs.logger.warning(
                "Configuration option 'theme' is deprecated and you should remove it from your configuration, HACS will know if you use 'theme' in your Home Assistant configuration, this option will be removed in a future release."
            )

    # Setup startup tasks
    if hacs.configuration.config_type == "yaml":
        hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START,
                                        hacs().startup_tasks())
    else:
        async_call_later(hacs.hass, 5, hacs().startup_tasks())

    # Show the configuration
    hacs.configuration.print()

    # Mischief managed!
    return True
Exemple #32
0
 def async_schedule_pull(self) -> None:
     """Schedule async_pull_messages to run."""
     self._unsub_refresh = async_call_later(self.hass, 1,
                                            self.async_pull_messages)
Exemple #33
0
    async def update(self, *args):
        data = token = None

        try:
            r = await self.session.get('https://yandex.ru/web-maps/covid19')
            text = await r.text()

            m = RE_HTML.search(text)
            data = json.loads(m[1])
            token = data['csrfToken']
            data = data['covidData']

        except Exception as e:
            _LOGGER.error(f"Load Data error: {e}")

        try:
            items = [p for p in data['items'] if p['name'] in self.include] \
                    if self.include else data['items']

            self._attrs = {
                p['name']: {
                    'cases':
                    p['cases'],
                    'cured':
                    p['cured'],
                    'deaths':
                    p['deaths'],
                    'new_cases':
                    (p['histogram'][-1]['value'] - p['histogram'][-2]['value'])
                    if 'histogram' in p and len(p['histogram']) >= 2 else 0
                }
                for p in items
            }

        except Exception as e:
            _LOGGER.error(f"Update Places error: {e}")

        if not self.include or 'Россия' in self.include:
            try:
                self._attrs['Россия'] = ru = {
                    'cases':
                    0,
                    'cured':
                    0,
                    'deaths':
                    0,
                    'new_cases': (data['histogram'][-1]['value'] -
                                  data['histogram'][-2]['value']),
                    'tests':
                    int(data['tests'].replace(' ', ''))
                }
                for p in data['items']:
                    if p.get('ru'):
                        ru['cases'] += p['cases']
                        ru['cured'] += p['cured']
                        ru['deaths'] += p['deaths']

            except Exception as e:
                _LOGGER.error(f"Update Russia error: {e}")

        if not self.include or 'Мир' in self.include:
            try:
                self._attrs['Мир'] = world = {
                    'cases': 0,
                    'cured': 0,
                    'deaths': 0
                }
                for p in data['items']:
                    if 'ru' not in p:
                        world['cases'] += p['cases']
                        world['cured'] += p['cured']
                        world['deaths'] += p['deaths']

            except Exception as e:
                _LOGGER.error(f"Update World error: {e}")

        try:
            ts = datetime.fromtimestamp(data['ts'])
            self._state = ts.strftime("%H:%M")

        except Exception as e:
            _LOGGER.error(f"Update Sensor error: {e}")

        try:
            r = await self.session.get('https://yandex.ru/web-maps/api/covid',
                                       params={
                                           'csrfToken': token,
                                           'isolation': 'true'
                                       })
            data = await r.json()

            if self.include:
                data['data']['cities'] = [
                    p for p in data['data']['cities']
                    if p['name'] in self.include
                ]

            for city in data['data']['cities']:
                name = city['name']
                if name in self._attrs:
                    self._attrs[name]['isolation'] = city['level']
                else:
                    self._attrs[name] = {'isolation': city['level']}

        except Exception as e:
            _LOGGER.error(f"Update Isolation error: {e}")

        self.async_schedule_update_ha_state()

        async_call_later(self.hass, 60 * 60, self.update)
    async def ws_connect(self):
        """Register handlers and connect to the websocket."""
        from aioambient.errors import WebsocketError

        async def _ws_reconnect(event_time):
            """Forcibly disconnect from and reconnect to the websocket."""
            _LOGGER.debug('Watchdog expired; forcing socket reconnection')
            await self.client.websocket.disconnect()
            await self.client.websocket.connect()

        def on_connect():
            """Define a handler to fire when the websocket is connected."""
            _LOGGER.info('Connected to websocket')
            _LOGGER.debug('Watchdog starting')
            self._watchdog_listener = async_call_later(
                self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)

        def on_data(data):
            """Define a handler to fire when the data is received."""
            mac_address = data['macAddress']
            if data != self.stations[mac_address][ATTR_LAST_DATA]:
                _LOGGER.debug('New data received: %s', data)
                self.stations[mac_address][ATTR_LAST_DATA] = data
                async_dispatcher_send(self._hass, TOPIC_UPDATE)

            _LOGGER.debug('Resetting watchdog')
            self._watchdog_listener()
            self._watchdog_listener = async_call_later(
                self._hass, DEFAULT_WATCHDOG_SECONDS, _ws_reconnect)

        def on_disconnect():
            """Define a handler to fire when the websocket is disconnected."""
            _LOGGER.info('Disconnected from websocket')

        def on_subscribed(data):
            """Define a handler to fire when the subscription is set."""
            for station in data['devices']:
                if station['macAddress'] in self.stations:
                    continue

                _LOGGER.debug('New station subscription: %s', data)

                # If the user hasn't specified monitored conditions, use only
                # those that their station supports (and which are defined
                # here):
                if not self.monitored_conditions:
                    self.monitored_conditions = [
                        k for k in station['lastData'].keys()
                        if k in SENSOR_TYPES
                    ]

                self.stations[station['macAddress']] = {
                    ATTR_LAST_DATA: station['lastData'],
                    ATTR_LOCATION: station.get('info', {}).get('location'),
                    ATTR_NAME:
                        station.get('info', {}).get(
                            'name', station['macAddress']),
                }

            for component in ('binary_sensor', 'sensor'):
                self._hass.async_create_task(
                    self._hass.config_entries.async_forward_entry_setup(
                        self._config_entry, component))

                self._ws_reconnect_delay = DEFAULT_SOCKET_MIN_RETRY

        self.client.websocket.on_connect(on_connect)
        self.client.websocket.on_data(on_data)
        self.client.websocket.on_disconnect(on_disconnect)
        self.client.websocket.on_subscribed(on_subscribed)

        try:
            await self.client.websocket.connect()
        except WebsocketError as err:
            _LOGGER.error("Error with the websocket connection: %s", err)

            self._ws_reconnect_delay = min(2 * self._ws_reconnect_delay, 480)

            async_call_later(
                self._hass, self._ws_reconnect_delay, self.ws_connect)
 def _fetch_callback(self, *_):
     async_call_later(
         self._hass,
         randrange(0, 60 * self._random_fetch_time_offset),
         self._fetch_now_callback,
     )
Exemple #36
0
 def try_again(err: str):
     # Retry
     secs = random.randint(30, 60)
     _LOGGER.error("Retrying in %i seconds: %s", secs, err)
     async_call_later(self.hass, secs, self.fetching_data)
Exemple #37
0
 async def almond_hass_start(_event):
     event.async_call_later(hass, ALMOND_SETUP_DELAY,
                            configure_almond)
Exemple #38
0
            siren_duration = duration
        if (tone := kwargs.get(ATTR_TONE)) is not None:
            siren_tone = tone
        if (level := kwargs.get(ATTR_VOLUME_LEVEL)) is not None:
            siren_level = int(level)
        await self._channel.issue_start_warning(
            mode=siren_tone,
            warning_duration=siren_duration,
            siren_level=siren_level,
            strobe=should_strobe,
            strobe_duty_cycle=50 if should_strobe else 0,
            strobe_intensity=strobe_level,
        )
        self._attr_is_on = True
        self._off_listener = async_call_later(self._zha_device.hass,
                                              siren_duration,
                                              self.async_set_off)
        self.async_write_ha_state()

    async def async_turn_off(self, **kwargs: Any) -> None:
        """Turn off siren."""
        await self._channel.issue_start_warning(
            mode=WARNING_DEVICE_MODE_STOP, strobe=WARNING_DEVICE_STROBE_NO)
        self._attr_is_on = False
        self.async_write_ha_state()

    @callback
    def async_set_off(self, _) -> None:
        """Set is_on to False and write HA state."""
        self._attr_is_on = False
        if self._off_listener:
    async def async_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.
        """
        try:
            if not self.enabled:
                return
        except AttributeError:
            pass
        email = self._login.email
        if (self.entity_id is None  # Device has not initialized yet
                or email not in self.hass.data[DATA_ALEXAMEDIA]["accounts"]
                or self._login.session.closed):
            return
        device = self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]["devices"][
            "media_player"][self.unique_id]
        seen_commands = (
            self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]
            ["websocket_commands"].keys() if "websocket_commands"
            in (self.hass.data[DATA_ALEXAMEDIA]["accounts"][email]) else None)
        await self.refresh(  # pylint: disable=unexpected-keyword-arg
            device,
            no_throttle=True)
        websocket_enabled = self.hass.data[DATA_ALEXAMEDIA]["accounts"][
            email].get("websocket")
        if (self.state in [STATE_PLAYING] and
                #  only enable polling if websocket not connected
            (not websocket_enabled or not seen_commands
             or not ("PUSH_AUDIO_PLAYER_STATE" in seen_commands
                     or "PUSH_MEDIA_CHANGE" in seen_commands
                     or "PUSH_MEDIA_PROGRESS_CHANGE" in seen_commands))):
            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,
                )
                async_call_later(
                    self.hass,
                    PLAY_SCAN_INTERVAL,
                    lambda _: self.async_schedule_update_ha_state(force_refresh
                                                                  =True),
                )
        elif self._should_poll:  # Not playing, one last poll
            self._should_poll = False
            if not websocket_enabled:
                _LOGGER.debug(
                    "Disabling polling and scheduling last update in"
                    " 300 seconds for %s",
                    self.name,
                )
                async_call_later(
                    self.hass,
                    300,
                    lambda _: self.async_schedule_update_ha_state(force_refresh
                                                                  =True),
                )
            else:
                _LOGGER.debug("Disabling polling for %s", self.name)
        self._last_update = util.utcnow()
        self.async_schedule_update_ha_state()
Exemple #40
0
    async def register_webhook(event):
        if CONF_WEBHOOK_ID not in entry.data:
            data = {**entry.data, CONF_WEBHOOK_ID: secrets.token_hex()}
            hass.config_entries.async_update_entry(entry, data=data)

        if hass.components.cloud.async_active_subscription():
            if CONF_CLOUDHOOK_URL not in entry.data:
                webhook_url = await hass.components.cloud.async_create_cloudhook(
                    entry.data[CONF_WEBHOOK_ID]
                )
                data = {**entry.data, CONF_CLOUDHOOK_URL: webhook_url}
                hass.config_entries.async_update_entry(entry, data=data)
            else:
                webhook_url = entry.data[CONF_CLOUDHOOK_URL]
        else:
            webhook_url = hass.components.webhook.async_generate_url(
                entry.data[CONF_WEBHOOK_ID]
            )

        if entry.data["auth_implementation"] == "cloud" and not webhook_url.startswith(
            "https://"
        ):
            _LOGGER.warning(
                "Webhook not registered - "
                "https and port 443 is required to register the webhook"
            )
            return

        try:
            webhook_register(
                hass,
                DOMAIN,
                "Netatmo",
                entry.data[CONF_WEBHOOK_ID],
                async_handle_webhook,
            )

            async def handle_event(event):
                """Handle webhook events."""
                if event["data"]["push_type"] == "webhook_activation":
                    if activation_listener is not None:
                        activation_listener()

                    if activation_timeout is not None:
                        activation_timeout()

            activation_listener = async_dispatcher_connect(
                hass,
                f"signal-{DOMAIN}-webhook-None",
                handle_event,
            )

            activation_timeout = async_call_later(hass, 10, unregister_webhook)

            await hass.async_add_executor_job(
                hass.data[DOMAIN][entry.entry_id][AUTH].addwebhook, webhook_url
            )
            _LOGGER.info("Register Netatmo webhook: %s", webhook_url)
        except pyatmo.ApiError as err:
            _LOGGER.error("Error during webhook registration - %s", err)

        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, unregister_webhook)
Exemple #41
0
async def hacs_startup(hacs):
    """HACS startup tasks."""
    hacs.logger.debug(f"Configuration type: {hacs.configuration.config_type}")
    hacs.version = const.VERSION
    hacs.logger.info(const.STARTUP)
    hacs.system.config_path = hacs.hass.config.path()
    hacs.system.ha_version = HAVERSION
    hacs.system.disabled = False
    hacs.github = AIOGitHub(hacs.configuration.token,
                            async_create_clientsession(hacs.hass))
    hacs.data = HacsData()

    # Check minimum version
    if not check_version(hacs):
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Check custom_updater
    if not check_custom_updater(hacs):
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Set up frontend
    await setup_frontend(hacs)

    # Load HACS
    if not await load_hacs_repository(hacs):
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    val = ValidateData()
    if not val.validate_local_data_file():
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False
    else:
        if os.path.exists(f"{hacs.system.config_path}/.storage/hacs"):
            os.remove(f"{hacs.system.config_path}/.storage/hacs")

    # Restore from storefiles
    if not await hacs.data.restore():
        hacs_repo = hacs().get_by_name("custom-components/hacs")
        hacs_repo.pending_restart = True
        if hacs.configuration.config_type == "flow":
            if hacs.configuration.config_entry is not None:
                await async_remove_entry(hacs.hass,
                                         hacs.configuration.config_entry)
        return False

    # Add aditional categories
    if hacs.configuration.appdaemon:
        const.ELEMENT_TYPES.append("appdaemon")
    if hacs.configuration.python_script:
        const.ELEMENT_TYPES.append("python_script")
    if hacs.configuration.theme:
        const.ELEMENT_TYPES.append("theme")
    hacs.common.categories = sorted(const.ELEMENT_TYPES)

    # Setup startup tasks
    if hacs.configuration.config_type == "yaml":
        hacs.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START,
                                        hacs().startup_tasks())
    else:
        async_call_later(hacs.hass, 5, hacs().startup_tasks())

    # Print DEV warning
    if hacs.configuration.dev:
        hacs.logger.error(const.DEV_MODE)
        hacs.hass.components.persistent_notification.create(
            title="HACS DEV MODE",
            message=const.DEV_MODE,
            notification_id="hacs_dev_mode",
        )

    # Add sensor
    add_sensor(hacs)

    # Set up services
    await add_services(hacs)

    # Mischief managed!
    return True
Exemple #42
0
    async def update_system_info(now):
        nonlocal system_info
        system_info = await async_get_system_info(hass)

        # Update system info every hour
        async_call_later(hass, 3600, update_system_info)
    async def update_devices(login_obj):
        """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. 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()
        existing_entities = hass.data[DATA_ALEXAMEDIA]["accounts"][email][
            "entities"]["media_player"].values()
        websocket_enabled = hass.data[DATA_ALEXAMEDIA]["accounts"][email].get(
            "websocket")
        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 = {}
        try:
            if new_devices:
                auth_info = await AlexaAPI.get_authentication(login_obj)
            devices = await AlexaAPI.get_devices(login_obj)
            bluetooth = await AlexaAPI.get_bluetooth(login_obj)
            preferences = await AlexaAPI.get_device_preferences(login_obj)
            dnd = await AlexaAPI.get_dnd_state(login_obj)
            raw_notifications = await AlexaAPI.get_notifications(login_obj)
            _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):
            _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_platform_callback)
            return
        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:
                if component == "notify":
                    hass.async_create_task(
                        async_load_platform(
                            hass,
                            component,
                            DOMAIN,
                            {
                                CONF_NAME: DOMAIN,
                                "config": cleaned_config
                            },
                            config,
                        ))
                else:
                    hass.async_add_job(
                        hass.config_entries.async_forward_entry_setup(
                            config_entry, component))

        hass.data[DATA_ALEXAMEDIA]["accounts"][email]["new_devices"] = False
        async_call_later(
            hass,
            scan_interval if not websocket_enabled else scan_interval * 10,
            lambda _: hass.async_create_task(
                update_devices(  # pylint: disable=unexpected-keyword-arg
                    login_obj,
                    no_throttle=True)),
        )
async def async_setup_entry(hass, config_entry):
    """Setup JLR InConnect component"""
    _async_import_options_from_data_if_missing(hass, config_entry)

    health_update_track = None

    def get_schema(schema_list):
        s = {}
        for schema in schema_list:
            s.update(eval(schema))
        return vol.Schema(s)

    data = JLRApiHandler(hass, config_entry)

    update_interval = config_entry.options.get(CONF_SCAN_INTERVAL,
                                               DEFAULT_SCAN_INTERVAL)
    health_update_interval = config_entry.options.get(
        CONF_HEALTH_UPDATE_INTERVAL, 0)

    try:
        if await data.async_connect():
            if not data.connection.vehicles:
                # No vehicles or wrong credentials
                _LOGGER.error(
                    "Unable to get vehicles from api.  Check credentials")
                return False
    except Exception:
        return False

    # Do first update
    await data.async_update()

    # Poll for updates in background
    _LOGGER.info("Update from InControl servers on {} minute interval".format(
        int(update_interval)))
    update_track = async_track_time_interval(
        hass,
        data.do_status_update,
        timedelta(minutes=update_interval),
    )

    # Schedule health update and repeat interval
    if health_update_interval and health_update_interval > 0:
        _LOGGER.info("Vehicle health update on {} minute interval.".format(
            int(health_update_interval)))
        # Do initial call to health_update service after HASS start up.
        # This speeds up restart.
        # 30 seconds should do it.
        async_call_later(hass, 30, data.do_health_update)

        health_update_track = async_track_time_interval(
            hass,
            data.do_health_update,
            timedelta(minutes=data.health_update_interval),
        )
    else:
        _LOGGER.info("Scheduled vehicle health update is disabled. " +
                     "Set interval in options to enable.")

    update_listener = config_entry.add_update_listener(_async_update_listener)

    hass.data[DOMAIN][config_entry.entry_id] = {
        JLR_DATA: data,
        STATUS_UPDATE_TRACKER: update_track,
        HEALTH_UPDATE_TRACKER: health_update_track,
        UPDATE_LISTENER: update_listener,
    }

    # for vehicle in data.vehicles:
    for platform in PLATFORMS:
        hass.async_create_task(
            hass.config_entries.async_forward_entry_setup(
                config_entry, platform))

    # Add services
    for service, service_info in JLR_SERVICES.items():
        _LOGGER.debug("Adding {} service".format(service))
        hass.services.async_register(
            DOMAIN,
            service,
            data.async_call_service,
            schema=get_schema(service_info.get("schema")),
        )

    # Create vehicle devices
    await async_update_device_registry(hass, config_entry,
                                       data.connection.vehicles, data)

    return True
Exemple #45
0
    async def update_devices(login_obj):
        """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. if
        websockets is connected, it will return immediately unless
        'new_devices' has been set to True.
        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
        existing_serials = (hass.data[DATA_ALEXAMEDIA]
                            ['accounts']
                            [email]
                            ['entities']
                            ['media_player'].keys() if 'entities' in (
                                hass.data[DATA_ALEXAMEDIA]
                                ['accounts']
                                [email])
                            else [])
        existing_entities = (hass.data[DATA_ALEXAMEDIA]
                             ['accounts']
                             [email]
                             ['entities']
                             ['media_player'].values())
        if ('websocket' in hass.data[DATA_ALEXAMEDIA]['accounts'][email]
            and hass.data[DATA_ALEXAMEDIA]['accounts'][email]['websocket']
                and not (hass.data[DATA_ALEXAMEDIA]
                         ['accounts'][email]['new_devices'])):
            return
        hass.data[DATA_ALEXAMEDIA]['accounts'][email]['new_devices'] = False
        devices = await AlexaAPI.get_devices(login_obj)
        bluetooth = await AlexaAPI.get_bluetooth(login_obj)
        preferences = await AlexaAPI.get_device_preferences(login_obj)
        dnd = await AlexaAPI.get_dnd_state(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[DATA_ALEXAMEDIA]
                         ['accounts'][email]['config'])):
            _LOGGER.debug("%s: Alexa API disconnected; attempting to relogin",
                          hide_email(email))
            await login_obj.login()
            await test_login_status(hass,
                                    config, login_obj, setup_platform_callback)
            return

        new_alexa_clients = []  # list of newly discovered device names
        exclude_filter = []
        include_filter = []
        for device in devices:
            if include and device['accountName'] not in include:
                include_filter.append(device['accountName'])
                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']
                 [device['serialNumber']]) = device
                continue
            elif exclude and device['accountName'] in exclude:
                exclude_filter.append(device['accountName'])
                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']
                 [device['serialNumber']]) = device
                continue

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

            if 'devicePreferences' in preferences:
                for dev in preferences['devicePreferences']:
                    if dev['deviceSerialNumber'] == device['serialNumber']:
                        device['locale'] = dev['locale']
                        _LOGGER.debug("Locale %s found for %s",
                                      device['locale'],
                                      hide_serial(device['serialNumber']))

            if 'doNotDisturbDeviceStatusList' in dnd:
                for dev in dnd['doNotDisturbDeviceStatusList']:
                    if dev['deviceSerialNumber'] == device['serialNumber']:
                        device['dnd'] = dev['enabled']
                        _LOGGER.debug("DND %s found for %s",
                                      device['dnd'],
                                      hide_serial(device['serialNumber']))

            (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 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_SCAN_INTERVAL, None)
            # CONF_SCAN_INTERVAL causes a json error in the recorder because it
            # is a timedelta object.
            cleaned_config.pop(CONF_PASSWORD, None)
            # CONF_PASSWORD contains sensitive info which is no longer needed
            for component in ALEXA_COMPONENTS:
                hass.async_create_task(
                    async_load_platform(hass,
                                        component,
                                        DOMAIN,
                                        {CONF_NAME: DOMAIN,
                                         "config": cleaned_config},
                                        config))

        # Process last_called data to fire events
        await update_last_called(login_obj)
        scan_interval = config.get(CONF_SCAN_INTERVAL)
        async_call_later(hass, scan_interval.total_seconds(), lambda _:
                         hass.async_create_task(
                            update_devices(login_obj,
                                           no_throttle=True)))
Exemple #46
0
 def try_again(err: str):
     """Retry in 15 to 20 minutes."""
     minutes = 15 + randrange(6)
     _LOGGER.error("Retrying in %i minutes: %s", minutes, err)
     async_call_later(self.hass, minutes*60, self.fetching_data)
Exemple #47
0
        def state_message_received(msg):
            """Handle a new received MQTT state message."""
            payload = msg.payload
            # auto-expire enabled?
            expire_after = self._config.get(CONF_EXPIRE_AFTER)

            if expire_after is not None and expire_after > 0:

                # When expire_after is set, and we receive a message, assume device is
                # not expired since it has to be to receive the message
                self._expired = False

                # Reset old trigger
                if self._expiration_trigger:
                    self._expiration_trigger()
                    self._expiration_trigger = None

                # Set new trigger
                expiration_at = dt_util.utcnow() + timedelta(seconds=expire_after)

                self._expiration_trigger = async_track_point_in_utc_time(
                    self.hass, self._value_is_expired, expiration_at
                )

            value_template = self._config.get(CONF_VALUE_TEMPLATE)
            if value_template is not None:
                payload = value_template.async_render_with_possible_json_value(
                    payload, variables={"entity_id": self.entity_id}
                )
                if not payload.strip():  # No output from template, ignore
                    _LOGGER.debug(
                        "Empty template output for entity: %s with state topic: %s. Payload: '%s', with value template '%s'",
                        self._config[CONF_NAME],
                        self._config[CONF_STATE_TOPIC],
                        msg.payload,
                        value_template,
                    )
                    return

            if payload == self._config[CONF_PAYLOAD_ON]:
                self._state = True
            elif payload == self._config[CONF_PAYLOAD_OFF]:
                self._state = False
            else:  # Payload is not for this entity
                template_info = ""
                if value_template is not None:
                    template_info = f", template output: '{payload}', with value template '{str(value_template)}'"
                _LOGGER.info(
                    "No matching payload found for entity: %s with state topic: %s. Payload: '%s'%s",
                    self._config[CONF_NAME],
                    self._config[CONF_STATE_TOPIC],
                    msg.payload,
                    template_info,
                )
                return

            if self._delay_listener is not None:
                self._delay_listener()
                self._delay_listener = None

            off_delay = self._config.get(CONF_OFF_DELAY)
            if self._state and off_delay is not None:
                self._delay_listener = evt.async_call_later(
                    self.hass, off_delay, off_delay_listener
                )

            self.async_write_ha_state()
Exemple #48
0
 async def lock_action(self, action):
     await self.hass.async_add_executor_job(self.kia_uvo_api.lock_action,
                                            self.token, action)
     self.lock_action_loop_count = 0
     self.lock_action_loop = async_call_later(self.hass, 5,
                                              self.force_update_loop)
Exemple #49
0
 def __init__(self, key, delay, params):
     self.key = key
     self.params = params
     async_call_later(_hass, delay, self.call)