Ejemplo n.º 1
0
    async def async_update_callback(self, device_id=None):
        """Let OPP know there has been an update from the controller."""
        # Track changes in connection state
        if not self._controller.is_connected and self._connected:
            # Connection has dropped
            self._connected = False
            reconnect_minutes = 1 + randrange(10)
            _LOGGER.error(
                "Connection to %s API was lost. Reconnecting in %i minutes",
                self._device_type,
                reconnect_minutes,
            )
            # Schedule reconnection

            async def try_connect(_now):
                await self._controller.connect()

            async_call_later(self.opp, reconnect_minutes * 60, try_connect)

        if self._controller.is_connected and not self._connected:
            # Connection has been restored
            self._connected = True
            _LOGGER.debug("Connection to %s API was restored", self._device_type)

        if not device_id or self._device_id == device_id:
            # Update all devices if no device_id was specified
            _LOGGER.debug(
                "%s API sent a status update for device %s",
                self._device_type,
                device_id,
            )
            self.async_schedule_update_op_state(True)
Ejemplo n.º 2
0
    async def fetching_data(self, *_):
        """Get the latest data from yr.no."""
        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.opp, minutes * 60, self.fetching_data)

        try:
            websession = async_get_clientsession(self.opp)
            with async_timeout.timeout(10):
                resp = await websession.get(self._url, params=self._urlparams)
            if resp.status != 200:
                try_again(f"{resp.url} returned {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.opp, 60 * 60, self.fetching_data)
Ejemplo n.º 3
0
    async def async_update(self):
        """Update the sensor."""
        await self._data.async_update()

        if not self.available:
            # Entity cannot be removed while its being added
            async_call_later(self.opp, 1, self._remove)
            return

        package = self._data.packages.get(self._tracking_number, None)

        # If the user has elected to not see delivered packages and one gets
        # delivered, post a notification:
        if package.status == VALUE_DELIVERED and not self._data.show_delivered:
            self._notify_delivered()
            # Entity cannot be removed while its being added
            async_call_later(self.opp, 1, self._remove)
            return

        self._attrs.update({
            ATTR_INFO_TEXT: package.info_text,
            ATTR_TIMESTAMP: package.timestamp,
            ATTR_LOCATION: package.location,
        })
        self._state = package.status
        self._friendly_name = package.friendly_name
Ejemplo n.º 4
0
    async def async_update_prices(self, now):
        """Update electricity prices from the ESIOS API."""
        prices = await self._pvpc_data.async_update_prices(now)
        if not prices and self._pvpc_data.source_available:
            self._num_retries += 1
            if self._num_retries > 2:
                _LOGGER.warning(
                    "%s: repeated bad data update, mark component as unavailable source",
                    self.entity_id,
                )
                self._pvpc_data.source_available = False
                return

            retry_delay = 2 * self._num_retries * self._pvpc_data.timeout
            _LOGGER.debug(
                "%s: Bad update[retry:%d], will try again in %d s",
                self.entity_id,
                self._num_retries,
                retry_delay,
            )
            async_call_later(self.opp, retry_delay, self.async_update_prices)
            return

        if not prices:
            _LOGGER.debug("%s: data source is not yet available",
                          self.entity_id)
            return

        self._num_retries = 0
        if not self._pvpc_data.source_available:
            self._pvpc_data.source_available = True
            _LOGGER.warning("%s: component has recovered data access",
                            self.entity_id)
            self.update_current_price(now)
Ejemplo n.º 5
0
 def _adapter_watchdog(now=None):
     _LOGGER.debug("Reached _adapter_watchdog")
     event.async_call_later(opp, WATCHDOG_INTERVAL, _adapter_watchdog)
     if not adapter.initialized:
         _LOGGER.info("Adapter not initialized; Trying to restart")
         opp.bus.fire(EVENT_HDMI_CEC_UNAVAILABLE)
         adapter.init()
Ejemplo n.º 6
0
    async def async_update(self) -> None:
        """Refresh the forecast data from SMHI weather API."""
        try:
            with async_timeout.timeout(10):
                self._forecasts = await self.get_weather_forecast()
                self._fail_count = 0

        except (asyncio.TimeoutError, SmhiForecastException):
            _LOGGER.error("Failed to connect to SMHI API, retry in 5 minutes")
            self._fail_count += 1
            if self._fail_count < 3:
                async_call_later(self.opp, RETRY_TIMEOUT, self.retry_update)
Ejemplo n.º 7
0
    def set_state(self, value):
        """Move switch state to value if call came from HomeKit."""
        _LOGGER.debug("%s: Set switch state to %s", self.entity_id, value)
        if self.activate_only and not value:
            _LOGGER.debug("%s: Ignoring turn_off call", self.entity_id)
            return
        params = {ATTR_ENTITY_ID: self.entity_id}
        service = SERVICE_TURN_ON if value else SERVICE_TURN_OFF
        self.async_call_service(self._domain, service, params)

        if self.activate_only:
            async_call_later(self.opp, 1, self.reset_switch)
Ejemplo n.º 8
0
    async def _attempt_connect(self):
        """Attempt to connect to the socket (retrying later on fail)."""
        async def connect(timestamp=None):
            """Connect."""
            await self.client.websocket.connect()

        try:
            await 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._opp, self._ws_reconnect_delay, connect)
Ejemplo n.º 9
0
 async def enable_alexa(_):
     """Enable Alexa."""
     aconf = await self.get_alexa_config()
     try:
         await aconf.async_enable_proactive_mode()
     except aiohttp.ClientError as err:  # If no internet available yet
         if self._opp.is_running:
             logging.getLogger(__package__).warning(
                 "Unable to activate Alexa Report State: %s. Retrying in 30 seconds",
                 err,
             )
         async_call_later(self._opp, 30, enable_alexa)
     except alexa_errors.NoTokenAvailable:
         pass
Ejemplo n.º 10
0
async def _async_stop_scripts_at_shutdown(opp, event):
    """Stop running Script objects started before shutdown."""
    async_call_later(opp, _SHUTDOWN_MAX_WAIT,
                     partial(_async_stop_scripts_after_shutdown, opp))

    running_scripts = [
        script for script in opp.data[DATA_SCRIPTS]
        if script["instance"].is_running and script["started_before_shutdown"]
    ]
    if running_scripts:
        names = ", ".join(
            [script["instance"].name for script in running_scripts])
        _LOGGER.debug("Stopping scripts running at shutdown: %s", names)
        await asyncio.gather(
            *[script["instance"].async_stop() for script in running_scripts])
Ejemplo n.º 11
0
    async def async_send(self, _):
        """Write preprocessed events to eventhub, with retry."""
        client = self._get_client()
        async with client:
            while not self.queue.empty():
                data_batch, dequeue_count = await self.fill_batch(client)
                _LOGGER.debug(
                    "Sending %d event(s), out of %d events in the queue",
                    len(data_batch),
                    dequeue_count,
                )
                if data_batch:
                    try:
                        await client.send_batch(data_batch)
                    except EventHubError as exc:
                        _LOGGER.error(
                            "Error in sending events to Event Hub: %s", exc)
                    finally:
                        for _ in range(dequeue_count):
                            self.queue.task_done()
        await client.close()

        if not self.shutdown:
            self._next_send_remover = async_call_later(self.opp,
                                                       self._send_interval,
                                                       self.async_send)
Ejemplo n.º 12
0
    def _handle_event(self, event, device_id):
        """Check if event applies to me and update."""
        if device_id != self._device_id:
            return

        _LOGGER.debug(
            "Binary sensor update (Device ID: %s Class: %s Sub: %s)",
            event.device.id_string,
            event.device.__class__.__name__,
            event.device.subtype,
        )

        self._apply_event(event)

        self.async_write_op_state()

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

        if self.is_on and self._off_delay is not None:

            @callback
            def off_delay_listener(now):
                """Switch device off after a delay."""
                self._delay_listener = None
                self._state = False
                self.async_write_op_state()

            self._delay_listener = evt.async_call_later(
                self.opp, self._off_delay, off_delay_listener
            )
Ejemplo n.º 13
0
    async def async_restart(self, _now: dt = None) -> None:
        """Restart the subscription assuming the camera rebooted."""
        if not self.started:
            return

        if self._subscription:
            # Suppressed. The subscription may no longer exist.
            with suppress(*SUBSCRIPTION_ERRORS):
                await self._subscription.Unsubscribe()
            self._subscription = None

        try:
            restarted = await self.async_start()
        except SUBSCRIPTION_ERRORS:
            restarted = False

        if not restarted:
            LOGGER.warning(
                "Failed to restart ONVIF PullPoint subscription for '%s'. Retrying",
                self.unique_id,
            )
            # Try again in a minute
            self._unsub_refresh = async_call_later(self.opp, 60, self.async_restart)
        elif self._listeners:
            LOGGER.debug(
                "Restarted ONVIF PullPoint subscription for '%s'", self.unique_id
            )
            self.async_schedule_pull()
Ejemplo n.º 14
0
 async def async_update_data(_now):
     await self.request_update()
     self._cancel_periodic_update = async_call_later(
         self.opp,
         self.config_entry.options[CONF_SCAN_INTERVAL],
         async_update_data,
     )
Ejemplo n.º 15
0
    async def _async_prefs_updated(self, prefs):
        """Handle updated preferences."""
        if ALEXA_DOMAIN not in self.opp.config.components and self.enabled:
            await async_setup_component(self.opp, ALEXA_DOMAIN, {})

        if self.should_report_state != self.is_reporting_states:
            if self.should_report_state:
                await self.async_enable_proactive_mode()
            else:
                await self.async_disable_proactive_mode()

            # State reporting is reported as a property on entities.
            # So when we change it, we need to sync all entities.
            await self.async_sync_entities()
            return

        # If user has filter in config.yaml, don't sync.
        if not self._config[CONF_FILTER].empty_filter:
            return

        # If entity prefs are the same, don't sync.
        if (self._cur_entity_prefs is prefs.alexa_entity_configs
                and self._cur_default_expose is prefs.alexa_default_expose):
            return

        if self._alexa_sync_unsub:
            self._alexa_sync_unsub()
            self._alexa_sync_unsub = None

        if self._cur_default_expose is not prefs.alexa_default_expose:
            await self.async_sync_entities()
            return

        self._alexa_sync_unsub = async_call_later(self.opp, SYNC_DELAY,
                                                  self._sync_prefs)
Ejemplo n.º 16
0
    async def register_webhook(event):
        if CONF_WEBHOOK_ID not in entry.data:
            data = {**entry.data, CONF_WEBHOOK_ID: secrets.token_hex()}
            opp.config_entries.async_update_entry(entry, data=data)

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

        if entry.data[
                "auth_implementation"] == cloud.DOMAIN 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(
                opp,
                DOMAIN,
                "Netatmo",
                entry.data[CONF_WEBHOOK_ID],
                async_handle_webhook,
            )

            async def handle_event(event):
                """Handle webhook events."""
                if event["data"][WEBHOOK_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(
                opp,
                f"signal-{DOMAIN}-webhook-None",
                handle_event,
            )

            activation_timeout = async_call_later(opp, 30, unregister_webhook)

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

        entry.async_on_unload(
            opp.bus.async_listen_once(EVENT_OPENPEERPOWER_STOP,
                                      unregister_webhook))
Ejemplo n.º 17
0
    def cluster_command(self, tsn, command_id, args):
        """Handle commands received to this cluster."""
        cmd = parse_and_log_command(self, tsn, command_id, args)

        if cmd in ("off", "off_with_effect"):
            self.attribute_updated(self.ON_OFF, False)
        elif cmd in ("on", "on_with_recall_global_scene"):
            self.attribute_updated(self.ON_OFF, True)
        elif cmd == "on_with_timed_off":
            should_accept = args[0]
            on_time = args[1]
            # 0 is always accept 1 is only accept when already on
            if should_accept == 0 or (should_accept == 1 and self._state):
                if self._off_listener is not None:
                    self._off_listener()
                    self._off_listener = None
                self.attribute_updated(self.ON_OFF, True)
                if on_time > 0:
                    self._off_listener = async_call_later(
                        self._ch_pool.opp,
                        (on_time / 10),  # value is in 10ths of a second
                        self.set_to_off,
                    )
        elif cmd == "toggle":
            self.attribute_updated(self.ON_OFF, not bool(self._state))
Ejemplo n.º 18
0
    def _set_state(self, state, _=None):
        """Set up auto off."""
        self._state = state
        self.async_set_context(self.coordinator.data["context"])
        self.async_write_op_state()

        if not state:
            return

        auto_off_time = self._rendered.get(CONF_AUTO_OFF) or self._config.get(
            CONF_AUTO_OFF)

        if auto_off_time is None:
            return

        if not isinstance(auto_off_time, timedelta):
            try:
                auto_off_time = cv.positive_time_period(auto_off_time)
            except vol.Invalid as err:
                logging.getLogger(__name__).warning(
                    "Error rendering %s template: %s", CONF_AUTO_OFF, err)
                return

        @callback
        def _auto_off(_):
            """Set state of template binary sensor."""
            self._state = False
            self.async_write_op_state()

        self._auto_off_cancel = async_call_later(self.opp,
                                                 auto_off_time.total_seconds(),
                                                 _auto_off)
Ejemplo n.º 19
0
    def _send_message(self, message: str | dict[str, Any]) -> None:
        """Send a message to the client.

        Closes connection if the client is not reading the messages.

        Async friendly.
        """
        if not isinstance(message, str):
            message = message_to_json(message)

        try:
            self._to_write.put_nowait(message)
        except asyncio.QueueFull:
            self._logger.error("Client exceeded max pending messages [2]: %s",
                               MAX_PENDING_MSG)

            self._cancel()

        if self._to_write.qsize() < PENDING_MSG_PEAK:
            if self._peak_checker_unsub:
                self._peak_checker_unsub()
                self._peak_checker_unsub = None
            return

        if self._peak_checker_unsub is None:
            self._peak_checker_unsub = async_call_later(
                self.opp, PENDING_MSG_PEAK_TIME, self._check_write_peak)
Ejemplo n.º 20
0
    def _update_state(self, result):
        super()._update_state(result)

        if self._delay_cancel:
            self._delay_cancel()
            self._delay_cancel = None

        state = (None if isinstance(result, TemplateError) else
                 template.result_as_boolean(result))

        if state == self._state:
            return

        # state without delay
        if (state is None or (state and not self._delay_on)
                or (not state and not self._delay_off)):
            self._state = state
            return

        @callback
        def _set_state(_):
            """Set state of template binary sensor."""
            self._state = state
            self.async_write_op_state()

        delay = (self._delay_on if state else self._delay_off).total_seconds()
        # state with delay. Cancelled if template result changes.
        self._delay_cancel = async_call_later(self.opp, delay, _set_state)
Ejemplo n.º 21
0
 async def async_got_disconnected(self, _=None):
     """Notification that we're disconnected from the HUB."""
     _LOGGER.debug("%s: disconnected from the HUB", self._name)
     # We're going to wait for 10 seconds before announcing we're
     # unavailable, this to allow a reconnection to happen.
     self._unsub_mark_disconnected = async_call_later(
         self.opp, TIME_MARK_DISCONNECTED, self._mark_disconnected_if_unavailable
     )
Ejemplo n.º 22
0
    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)
            self._unsub_fetch_data = async_call_later(self.opp, minutes * 60,
                                                      self._fetch_data)
            return

        # Wait between 55-65 minutes. If people update HA on the hour, this
        # will make sure it will spread it out.

        self._unsub_fetch_data = async_call_later(self.opp,
                                                  randrange(55, 65) * 60,
                                                  self._fetch_data)
        self._update()
Ejemplo n.º 23
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.opp, UNLOCK_MAINTAIN_TIME,
                                 self.clear_unlock_state)
                return True

        return False
Ejemplo n.º 24
0
    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/openpeerpower/core/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._opp, 120, self._async_set_no_motion
                )

            if self.entity_id is not None:
                self._opp.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
Ejemplo n.º 25
0
 async def interval_listener(now=None):
     """Handle elapsed interval with backoff."""
     nonlocal interval, remove
     try:
         if await action():
             interval = MIN_INTERVAL
         else:
             interval = min(interval * 2, MAX_INTERVAL)
     finally:
         remove = async_call_later(opp, interval, interval_listener)
Ejemplo n.º 26
0
async def _get_services(opp):
    """Get the available services."""
    services = opp.data.get(DATA_SERVICES)

    if services is not None:
        return services

    services = await account_link.async_fetch_available_services(opp.data[DOMAIN])

    opp.data[DATA_SERVICES] = services

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

    event.async_call_later(opp, CACHE_TIMEOUT, clear_services)

    return services
Ejemplo n.º 27
0
    def async_schedule_google_sync(self, agent_user_id: str):
        """Schedule a sync."""
        async def _schedule_callback(_now):
            """Handle a scheduled sync callback."""
            self._google_sync_unsub.pop(agent_user_id, None)
            await self.async_sync_entities(agent_user_id)

        self._google_sync_unsub.pop(agent_user_id, lambda: None)()

        self._google_sync_unsub[agent_user_id] = async_call_later(
            self.opp, SYNC_DELAY, _schedule_callback)
Ejemplo n.º 28
0
    async def async_turn(self, command):
        """Evaluate switch result."""
        result = await self._hub.async_pymodbus_call(
            self._slave, self._address, command, self._write_type
        )
        if result is None:
            self._available = False
            self.async_write_op_state()
            return

        self._available = True
        if not self._verify_active:
            self._is_on = command == self.command_on
            self.async_write_op_state()
            return

        if self._verify_delay:
            async_call_later(self.opp, self._verify_delay, self.async_update)
        else:
            await self.async_update()
Ejemplo n.º 29
0
    async def async_dump_service(call: ServiceCall):
        """Handle MQTT dump service calls."""
        messages = []

        @callback
        def collect_msg(msg):
            messages.append((msg.topic, msg.payload.replace("\n", "")))

        unsub = await async_subscribe(opp, call.data["topic"], collect_msg)

        def write_dump():
            with open(opp.config.path("mqtt_dump.txt"), "wt") as fp:
                for msg in messages:
                    fp.write(",".join(msg) + "\n")

        async def finish_dump(_):
            """Write dump to file."""
            unsub()
            await opp.async_add_executor_job(write_dump)

        event.async_call_later(opp, call.data["duration"], finish_dump)
Ejemplo n.º 30
0
 async def interval_listener(now):
     """Handle elapsed intervals with backoff."""
     nonlocal failed, remove
     try:
         failed += 1
         if await action(now):
             failed = 0
     finally:
         delay = intervals[failed] if failed < len(
             intervals) else intervals[-1]
         remove = async_call_later(opp, delay.total_seconds(),
                                   interval_listener)