Esempio n. 1
0
    async def async_initialize(self):
        """Perform async initialization of config."""
        await super().async_initialize()

        async def hass_started(hass):
            if self.enabled and GOOGLE_DOMAIN not in self.hass.config.components:
                await async_setup_component(self.hass, GOOGLE_DOMAIN, {})

        start.async_at_start(self.hass, hass_started)

        # Remove any stored user agent id that is not ours
        remove_agent_user_ids = []
        for agent_user_id in self._store.agent_user_ids:
            if agent_user_id != self.agent_user_id:
                remove_agent_user_ids.append(agent_user_id)

        for agent_user_id in remove_agent_user_ids:
            await self.async_disconnect_agent_user(agent_user_id)

        self._prefs.async_listen_updates(self._async_prefs_updated)

        self.hass.bus.async_listen(
            er.EVENT_ENTITY_REGISTRY_UPDATED,
            self._handle_entity_registry_updated,
        )
        self.hass.bus.async_listen(
            dr.EVENT_DEVICE_REGISTRY_UPDATED,
            self._handle_device_registry_updated,
        )
Esempio n. 2
0
    async def async_get_instance(hass: HomeAssistant) -> RestoreStateData:
        """Get the singleton instance of this data helper."""
        data = RestoreStateData(hass)

        try:
            stored_states = await data.store.async_load()
        except HomeAssistantError as exc:
            _LOGGER.error("Error loading last states", exc_info=exc)
            stored_states = None

        if stored_states is None:
            _LOGGER.debug("Not creating cache - no saved states found")
            data.last_states = {}
        else:
            data.last_states = {
                item["state"]["entity_id"]: StoredState.from_dict(item)
                for item in stored_states
                if valid_entity_id(item["state"]["entity_id"])
            }
            _LOGGER.debug("Created cache with %s", list(data.last_states))

        async def hass_start(hass: HomeAssistant) -> None:
            """Start the restore state task."""
            data.async_setup_dump()

        start.async_at_start(hass, hass_start)

        return data
Esempio n. 3
0
    async def async_added_to_hass(self) -> None:
        """Register listeners."""
        async def _update_at_start(_):
            self.async_update_group_state()
            self.async_write_ha_state()

        start.async_at_start(self.hass, _update_at_start)
Esempio n. 4
0
    async def async_initialize(self):
        """Initialize the Alexa config."""

        async def hass_started(hass):
            if self.enabled and ALEXA_DOMAIN not in self.hass.config.components:
                await async_setup_component(self.hass, ALEXA_DOMAIN, {})

        start.async_at_start(self.hass, hass_started)
async def test_at_start_when_running(hass):
    """Test at start when already running."""
    assert hass.is_running

    calls = []

    async def cb_at_start(hass):
        """Home Assistant is started."""
        calls.append(1)

    start.async_at_start(hass, cb_at_start)
    await hass.async_block_till_done()
    assert len(calls) == 1
Esempio n. 6
0
    async def async_initialize(self):
        """Perform async initialization of config."""
        self._store = GoogleConfigStore(self.hass)
        await self._store.async_load()

        if not self.enabled:
            return

        async def sync_google(_):
            """Sync entities to Google."""
            await self.async_sync_entities_all()

        start.async_at_start(self.hass, sync_google)
Esempio n. 7
0
    async def async_initialize(self):
        """Initialize the Alexa config."""
        async def hass_started(hass):
            if self.enabled and ALEXA_DOMAIN not in self.hass.config.components:
                await async_setup_component(self.hass, ALEXA_DOMAIN, {})

        start.async_at_start(self.hass, hass_started)

        self._prefs.async_listen_updates(self._async_prefs_updated)
        self.hass.bus.async_listen(
            entity_registry.EVENT_ENTITY_REGISTRY_UPDATED,
            self._handle_entity_registry_updated,
        )
Esempio n. 8
0
async def test_at_start_when_starting_callback(hass, caplog):
    """Test at start when yet to start."""
    hass.state = core.CoreState.not_running
    assert not hass.is_running

    calls = []

    @core.callback
    def cb_at_start(hass):
        """Home Assistant is started."""
        calls.append(1)

    cancel = start.async_at_start(hass, cb_at_start)
    await hass.async_block_till_done()
    assert len(calls) == 0

    hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
    await hass.async_block_till_done()
    assert len(calls) == 1

    cancel()

    # Check the unnecessary cancel did not generate warnings or errors
    for record in caplog.records:
        assert record.levelname in ("DEBUG", "INFO")
Esempio n. 9
0
    async def async_added_to_hass(self) -> None:
        """Wait for start so origin and destination entities can be resolved."""
        await super().async_added_to_hass()

        async def _update_at_start(_):
            await self.async_update()

        self.async_on_remove(async_at_start(self.hass, _update_at_start))
Esempio n. 10
0
async def test_at_start_when_starting_awaitable(hass):
    """Test at start when yet to start."""
    hass.state = core.CoreState.not_running
    assert not hass.is_running

    calls = []

    async def cb_at_start(hass):
        """Home Assistant is started."""
        calls.append(1)

    start.async_at_start(hass, cb_at_start)
    await hass.async_block_till_done()
    assert len(calls) == 0

    hass.bus.async_fire(EVENT_HOMEASSISTANT_START)
    await hass.async_block_till_done()
    assert len(calls) == 1
Esempio n. 11
0
async def test_cancelling_when_running(hass, caplog):
    """Test cancelling at start when already running."""
    assert hass.state == core.CoreState.running
    assert hass.is_running

    calls = []

    async def cb_at_start(hass):
        """Home Assistant is started."""
        calls.append(1)

    start.async_at_start(hass, cb_at_start)()
    await hass.async_block_till_done()
    assert len(calls) == 1

    # Check the unnecessary cancel did not generate warnings or errors
    for record in caplog.records:
        assert record.levelname in ("DEBUG", "INFO")
Esempio n. 12
0
async def test_at_start_when_running_callback(hass):
    """Test at start when already running."""
    assert hass.state == core.CoreState.running
    assert hass.is_running

    calls = []

    @core.callback
    def cb_at_start(hass):
        """Home Assistant is started."""
        calls.append(1)

    start.async_at_start(hass, cb_at_start)
    assert len(calls) == 1

    hass.state = core.CoreState.starting
    assert hass.is_running

    start.async_at_start(hass, cb_at_start)
    assert len(calls) == 2
    async def async_startup():
        """HACS startup tasks."""
        hacs.enable_hacs()

        for location in (
            hass.config.path("custom_components/custom_updater.py"),
            hass.config.path("custom_components/custom_updater/__init__.py"),
        ):
            if os.path.exists(location):
                hacs.log.critical(
                    "This cannot be used with custom_updater. "
                    "To use this you need to remove custom_updater form %s",
                    location,
                )

                hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
                return False

        if not version_left_higher_or_equal_then_right(
            hacs.core.ha_version.string,
            MINIMUM_HA_VERSION,
        ):
            hacs.log.critical(
                "You need HA version %s or newer to use this integration.",
                MINIMUM_HA_VERSION,
            )
            hacs.disable_hacs(HacsDisabledReason.CONSTRAINS)
            return False

        if not await hacs.data.restore():
            hacs.disable_hacs(HacsDisabledReason.RESTORE)
            return False

        can_update = await hacs.async_can_update()
        hacs.log.debug("Can update %s repositories", can_update)

        hacs.set_active_categories()

        async_register_websocket_commands(hass)
        async_register_frontend(hass, hacs)

        if hacs.configuration.config_type == ConfigurationType.YAML:
            hass.async_create_task(
                async_load_platform(hass, Platform.SENSOR, DOMAIN, {}, hacs.configuration.config)
            )
            hacs.log.info("Update entities are only supported when using UI configuration")

        else:
            if hacs.configuration.experimental:
                hass.config_entries.async_setup_platforms(
                    hacs.configuration.config_entry, [Platform.SENSOR, Platform.UPDATE]
                )
            else:
                hass.config_entries.async_setup_platforms(
                    hacs.configuration.config_entry, [Platform.SENSOR]
                )

        hacs.set_stage(HacsStage.SETUP)
        if hacs.system.disabled:
            return False

        # Schedule startup tasks
        async_at_start(hass=hass, at_start_cb=hacs.startup_tasks)

        hacs.set_stage(HacsStage.WAITING)
        hacs.log.info("Setup complete, waiting for Home Assistant before startup tasks starts")

        return not hacs.system.disabled
Esempio n. 14
0
 def _async_add_listener(self) -> None:
     """Add a listener to start tracking state changes after start."""
     self._at_start_listener = async_at_start(
         self.hass, self._async_add_events_listener)
Esempio n. 15
0
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
    """Set up from a config entry."""
    http_session = async_get_clientsession(hass, verify_ssl=False)
    db_file = hass.config.path("music_assistant.db")

    # databases is really chatty with logging at info level
    logging.getLogger("databases").setLevel(logging.WARNING)

    conf = entry.options

    # TODO: adjust config flow to support creating multiple provider entries
    providers = []

    if conf.get(CONF_SPOTIFY_ENABLED):
        providers.append(
            MusicProviderConfig(
                ProviderType.SPOTIFY,
                username=conf.get(CONF_SPOTIFY_USERNAME),
                password=conf.get(CONF_SPOTIFY_PASSWORD),
            ))

    if conf.get(CONF_QOBUZ_ENABLED):
        providers.append(
            MusicProviderConfig(
                ProviderType.QOBUZ,
                username=conf.get(CONF_QOBUZ_USERNAME),
                password=conf.get(CONF_QOBUZ_PASSWORD),
            ))

    if conf.get(CONF_TUNEIN_ENABLED):
        providers.append(
            MusicProviderConfig(
                ProviderType.TUNEIN,
                username=conf.get(CONF_TUNEIN_USERNAME),
            ))
    if conf.get(CONF_FILE_ENABLED) and conf.get(CONF_FILE_DIRECTORY):
        providers.append(
            MusicProviderConfig(
                ProviderType.FILESYSTEM_LOCAL,
                path=conf.get(CONF_FILE_DIRECTORY),
            ))
    stream_ip = get_local_ip_from_internal_url(hass)
    mass_conf = MassConfig(database_url=f"sqlite:///{db_file}",
                           providers=providers,
                           stream_ip=stream_ip)

    mass = MusicAssistant(mass_conf, session=http_session)

    try:
        await mass.setup()
    except MusicAssistantError as err:
        await mass.stop()
        LOGGER.exception(err)
        raise ConfigEntryNotReady from err
    except Exception as exc:  # pylint: disable=broad-except
        await mass.stop()
        raise exc

    hass.data[DOMAIN] = mass

    # initialize platforms
    if conf.get(CONF_CREATE_MASS_PLAYERS, True):
        hass.config_entries.async_setup_platforms(entry, PLATFORMS)

    async def on_hass_start(*args, **kwargs):
        """Start sync actions when Home Assistant is started."""
        register_services(hass, mass)
        # register hass players with mass
        await async_register_player_controls(hass, mass, entry)
        # start and schedule sync (every 3 hours)
        await mass.music.start_sync(schedule=3)

    async def on_hass_stop(event: Event):
        """Handle an incoming stop event from Home Assistant."""
        await mass.stop()

    async def on_mass_event(event: MassEvent):
        """Handle an incoming event from Music Assistant."""
        # forward event to the HA eventbus
        if hasattr(event.data, "to_dict"):
            data = event.data.to_dict()
        else:
            data = event.data
        hass.bus.async_fire(
            DOMAIN_EVENT,
            {
                "type": event.type.value,
                "object_id": event.object_id,
                "data": data
            },
        )

    # setup event listeners, register their unsubscribe in the unload
    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop))
    entry.async_on_unload(async_at_start(hass, on_hass_start))
    entry.async_on_unload(entry.add_update_listener(_update_listener))
    entry.async_on_unload(
        hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, on_hass_stop))
    entry.async_on_unload(entry.add_update_listener(_update_listener))
    entry.async_on_unload(mass.subscribe(on_mass_event, FORWARD_EVENTS))

    # Websocket support and frontend (panel)
    async_register_websockets(hass)
    entry.async_on_unload(await async_register_panel(hass, entry.title))

    # cleanup orphan devices/entities
    dev_reg = dr.async_get(hass)
    stored_devices = dr.async_entries_for_config_entry(dev_reg, entry.entry_id)
    if CONF_PLAYER_ENTITIES in entry.options:
        for device in stored_devices:
            for _, player_id in device.identifiers:
                if player_id not in entry.options[CONF_PLAYER_ENTITIES]:
                    dev_reg.async_remove_device(device.id)
                elif not entry.options[CONF_CREATE_MASS_PLAYERS]:
                    dev_reg.async_remove_device(device.id)
    return True
Esempio n. 16
0
async def async_setup_platform(
    hass: HomeAssistant,
    config: ConfigType,
    async_add_entities: AddEntitiesCallback,
    discovery_info: DiscoveryInfoType | None = None,
) -> None:
    """Set up the SAJ sensors."""

    remove_interval_update = None
    wifi = config[CONF_TYPE] == INVERTER_TYPES[1]

    # Init all sensors
    sensor_def = pysaj.Sensors(wifi)

    # Use all sensors by default
    hass_sensors = []

    kwargs = {}
    if wifi:
        kwargs["wifi"] = True
        if config.get(CONF_USERNAME) and config.get(CONF_PASSWORD):
            kwargs["username"] = config[CONF_USERNAME]
            kwargs["password"] = config[CONF_PASSWORD]

    try:
        saj = pysaj.SAJ(config[CONF_HOST], **kwargs)
        done = await saj.read(sensor_def)
    except pysaj.UnauthorizedException:
        _LOGGER.error("Username and/or password is wrong")
        return
    except pysaj.UnexpectedResponseException as err:
        _LOGGER.error(
            "Error in SAJ, please check host/ip address. Original error: %s",
            err)
        return

    if not done:
        raise PlatformNotReady

    for sensor in sensor_def:
        if sensor.enabled:
            hass_sensors.append(
                SAJsensor(saj.serialnumber,
                          sensor,
                          inverter_name=config.get(CONF_NAME)))

    async_add_entities(hass_sensors)

    async def async_saj():
        """Update all the SAJ sensors."""
        values = await saj.read(sensor_def)

        for sensor in hass_sensors:
            state_unknown = False
            # SAJ inverters are powered by DC via solar panels and thus are
            # offline after the sun has set. If a sensor resets on a daily
            # basis like "today_yield", this reset won't happen automatically.
            # Code below checks if today > day when sensor was last updated
            # and if so: set state to None.
            # Sensors with live values like "temperature" or "current_power"
            # will also be reset to None.
            if not values and (
                (sensor.per_day_basis and date.today() > sensor.date_updated)
                    or
                (not sensor.per_day_basis and not sensor.per_total_basis)):
                state_unknown = True
            sensor.async_update_values(unknown_state=state_unknown)

        return values

    @callback
    def start_update_interval(event):
        """Start the update interval scheduling."""
        nonlocal remove_interval_update
        remove_interval_update = async_track_time_interval_backoff(
            hass, async_saj)

    @callback
    def stop_update_interval(event):
        """Properly cancel the scheduled update."""
        remove_interval_update()  # pylint: disable=not-callable

    hass.bus.async_listen(EVENT_HOMEASSISTANT_STOP, stop_update_interval)
    async_at_start(hass, start_update_interval)
Esempio n. 17
0
class StatisticsSensor(SensorEntity):
    """Representation of a Statistics sensor."""

    def __init__(
        self,
        source_entity_id,
        name,
        state_characteristic,
        samples_max_buffer_size,
        samples_max_age,
        precision,
        quantile_intervals,
        quantile_method,
    ):
        """Initialize the Statistics sensor."""
        self._source_entity_id = source_entity_id
        self.is_binary = self._source_entity_id.split(".")[0] == "binary_sensor"
        self._name = name
        self._state_characteristic = state_characteristic
        if self._state_characteristic == STAT_DEFAULT:
            self._state_characteristic = STAT_COUNT if self.is_binary else STAT_MEAN
            _LOGGER.warning(DEPRECATION_WARNING, self._state_characteristic, name)
        self._samples_max_buffer_size = samples_max_buffer_size
        self._samples_max_age = samples_max_age
        self._precision = precision
        self._quantile_intervals = quantile_intervals
        self._quantile_method = quantile_method
        self._value = None
        self._unit_of_measurement = None
        self._available = False
        self.states = deque(maxlen=self._samples_max_buffer_size)
        self.ages = deque(maxlen=self._samples_max_buffer_size)
        self.attributes = {
            STAT_AGE_COVERAGE_RATIO: None,
            STAT_BUFFER_USAGE_RATIO: None,
            STAT_SOURCE_VALUE_VALID: None,
        }

        if self.is_binary:
            self._state_characteristic_fn = getattr(
                self, f"_stat_binary_{self._state_characteristic}"
            )
        else:
            self._state_characteristic_fn = getattr(
                self, f"_stat_{self._state_characteristic}"
            )

        self._update_listener = None

    async def async_added_to_hass(self):
        """Register callbacks."""

        @callback
        def async_stats_sensor_state_listener(event):
            """Handle the sensor state changes."""
            if (new_state := event.data.get("new_state")) is None:
                return
            self._add_state_to_queue(new_state)
            self.async_schedule_update_ha_state(True)

        async def async_stats_sensor_startup(_):
            """Add listener and get recorded state."""
            _LOGGER.debug("Startup for %s", self.entity_id)

            self.async_on_remove(
                async_track_state_change_event(
                    self.hass,
                    [self._source_entity_id],
                    async_stats_sensor_state_listener,
                )
            )

            if "recorder" in self.hass.config.components:
                self.hass.async_create_task(self._initialize_from_database())

        async_at_start(self.hass, async_stats_sensor_startup)
Esempio n. 18
0
class StatisticsSensor(SensorEntity):
    """Representation of a Statistics sensor."""
    def __init__(
        self,
        source_entity_id: str,
        name: str,
        unique_id: str | None,
        state_characteristic: str,
        samples_max_buffer_size: int,
        samples_max_age: timedelta | None,
        precision: int,
        quantile_intervals: int,
        quantile_method: Literal["exclusive", "inclusive"],
    ) -> None:
        """Initialize the Statistics sensor."""
        self._attr_icon: str = ICON
        self._attr_name: str = name
        self._attr_should_poll: bool = False
        self._attr_unique_id: str | None = unique_id
        self._source_entity_id: str = source_entity_id
        self.is_binary: bool = (split_entity_id(
            self._source_entity_id)[0] == BINARY_SENSOR_DOMAIN)
        self._state_characteristic: str = state_characteristic
        self._samples_max_buffer_size: int = samples_max_buffer_size
        self._samples_max_age: timedelta | None = samples_max_age
        self._precision: int = precision
        self._quantile_intervals: int = quantile_intervals
        self._quantile_method: Literal["exclusive",
                                       "inclusive"] = quantile_method
        self._value: StateType | datetime = None
        self._unit_of_measurement: str | None = None
        self._available: bool = False
        self.states: deque[float | bool] = deque(
            maxlen=self._samples_max_buffer_size)
        self.ages: deque[datetime] = deque(
            maxlen=self._samples_max_buffer_size)
        self.attributes: dict[str, StateType] = {
            STAT_AGE_COVERAGE_RATIO: None,
            STAT_BUFFER_USAGE_RATIO: None,
            STAT_SOURCE_VALUE_VALID: None,
        }

        self._state_characteristic_fn: Callable[[], StateType | datetime]
        if self.is_binary:
            self._state_characteristic_fn = getattr(
                self, f"_stat_binary_{self._state_characteristic}")
        else:
            self._state_characteristic_fn = getattr(
                self, f"_stat_{self._state_characteristic}")

        self._update_listener: CALLBACK_TYPE | None = None

    async def async_added_to_hass(self) -> None:
        """Register callbacks."""
        @callback
        def async_stats_sensor_state_listener(event: Event) -> None:
            """Handle the sensor state changes."""
            if (new_state := event.data.get("new_state")) is None:
                return
            self._add_state_to_queue(new_state)
            self.async_schedule_update_ha_state(True)

        async def async_stats_sensor_startup(_: HomeAssistant) -> None:
            """Add listener and get recorded state."""
            _LOGGER.debug("Startup for %s", self.entity_id)

            self.async_on_remove(
                async_track_state_change_event(
                    self.hass,
                    [self._source_entity_id],
                    async_stats_sensor_state_listener,
                ))

            if "recorder" in self.hass.config.components:
                self.hass.async_create_task(self._initialize_from_database())

        self.async_on_remove(
            async_at_start(self.hass, async_stats_sensor_startup))
Esempio n. 19
0
 async def async_added_to_hass(self):
     """Handle addition to Home Assistant."""
     self.async_on_remove(start.async_at_start(self.hass, self._async_start))
Esempio n. 20
0
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
    """Set up alerts."""
    last_alerts: dict[str, str | None] = {}

    async def async_update_alerts() -> None:
        nonlocal last_alerts

        active_alerts: dict[str, str | None] = {}

        for issue_id, alert in coordinator.data.items():
            # Skip creation if already created and not updated since then
            if issue_id in last_alerts and alert.date_updated == last_alerts[
                    issue_id]:
                active_alerts[issue_id] = alert.date_updated
                continue

            # Fetch alert to get title + description
            try:
                response = await async_get_clientsession(hass).get(
                    f"https://alerts.home-assistant.io/alerts/{alert.filename}",
                    timeout=aiohttp.ClientTimeout(total=10),
                )
            except asyncio.TimeoutError:
                _LOGGER.warning("Error fetching %s: timeout", alert.filename)
                continue

            alert_content = await response.text()
            alert_parts = alert_content.split("---")

            if len(alert_parts) != 3:
                _LOGGER.warning("Error parsing %s: unexpected metadata format",
                                alert.filename)
                continue

            try:
                alert_info = parse_yaml(alert_parts[1])
            except ValueError as err:
                _LOGGER.warning("Error parsing %s metadata: %s",
                                alert.filename, err)
                continue

            if not isinstance(alert_info, dict) or "title" not in alert_info:
                _LOGGER.warning("Error in %s metadata: title not found",
                                alert.filename)
                continue

            alert_title = alert_info["title"]
            alert_content = alert_parts[2].strip()

            async_create_issue(
                hass,
                DOMAIN,
                issue_id,
                is_fixable=False,
                issue_domain=alert.integration,
                severity=IssueSeverity.WARNING,
                translation_key="alert",
                translation_placeholders={
                    "title": alert_title,
                    "description": alert_content,
                },
            )
            active_alerts[issue_id] = alert.date_updated

        inactive_alerts = last_alerts.keys() - active_alerts.keys()
        for issue_id in inactive_alerts:
            async_delete_issue(hass, DOMAIN, issue_id)

        last_alerts = active_alerts

    @callback
    def async_schedule_update_alerts() -> None:
        if not coordinator.last_update_success:
            return

        hass.async_create_task(async_update_alerts())

    coordinator = AlertUpdateCoordinator(hass)
    coordinator.async_add_listener(async_schedule_update_alerts)

    async def initial_refresh(hass: HomeAssistant) -> None:
        await coordinator.async_refresh()

    async_at_start(hass, initial_refresh)

    return True