578 lines
21 KiB
Python
578 lines
21 KiB
Python
"""The PowerCalc integration."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
from functools import partial
|
|
import logging
|
|
import random
|
|
import time
|
|
from typing import Any
|
|
|
|
from awesomeversion.awesomeversion import AwesomeVersion
|
|
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
|
|
from homeassistant.components.utility_meter import max_28_days
|
|
from homeassistant.components.utility_meter.const import METER_TYPES
|
|
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
|
|
from homeassistant.const import (
|
|
CONF_DOMAIN,
|
|
CONF_ENABLED,
|
|
EVENT_HOMEASSISTANT_STARTED,
|
|
Platform,
|
|
__version__ as HA_VERSION, # noqa: N812
|
|
)
|
|
from homeassistant.core import Event, HassJob, HomeAssistant, ServiceCall, callback
|
|
import homeassistant.helpers.config_validation as cv
|
|
from homeassistant.helpers.discovery import async_load_platform
|
|
from homeassistant.helpers.entity_platform import async_get_platforms
|
|
import homeassistant.helpers.entity_registry as er
|
|
from homeassistant.helpers.event import async_call_later, async_track_time_interval
|
|
from homeassistant.helpers.reload import async_integration_yaml_config
|
|
from homeassistant.helpers.typing import ConfigType
|
|
import voluptuous as vol
|
|
|
|
from .analytics.analytics import ANALYTICS_INTERVAL, Analytics
|
|
from .common import validate_name_pattern
|
|
from .configuration.global_config import (
|
|
FLAG_HAS_GLOBAL_GUI_CONFIG,
|
|
get_global_configuration,
|
|
)
|
|
from .const import (
|
|
CONF_CREATE_DOMAIN_GROUPS,
|
|
CONF_CREATE_ENERGY_SENSORS,
|
|
CONF_CREATE_STANDBY_GROUP,
|
|
CONF_CREATE_UTILITY_METERS,
|
|
CONF_DISABLE_EXTENDED_ATTRIBUTES,
|
|
CONF_DISABLE_LIBRARY_DOWNLOAD,
|
|
CONF_DISCOVERY,
|
|
CONF_DISCOVERY_EXCLUDE_DEVICE_TYPES_DEPRECATED,
|
|
CONF_DISCOVERY_EXCLUDE_SELF_USAGE_DEPRECATED,
|
|
CONF_ENABLE_ANALYTICS,
|
|
CONF_ENABLE_AUTODISCOVERY_DEPRECATED,
|
|
CONF_ENERGY_INTEGRATION_METHOD,
|
|
CONF_ENERGY_SENSOR_CATEGORY,
|
|
CONF_ENERGY_SENSOR_FRIENDLY_NAMING,
|
|
CONF_ENERGY_SENSOR_NAMING,
|
|
CONF_ENERGY_SENSOR_PRECISION,
|
|
CONF_ENERGY_SENSOR_UNIT_PREFIX,
|
|
CONF_ENERGY_UPDATE_INTERVAL,
|
|
CONF_EXCLUDE_DEVICE_TYPES,
|
|
CONF_EXCLUDE_SELF_USAGE,
|
|
CONF_FORCE_UPDATE_FREQUENCY_DEPRECATED,
|
|
CONF_GROUP_ENERGY_UPDATE_INTERVAL,
|
|
CONF_GROUP_POWER_UPDATE_INTERVAL,
|
|
CONF_GROUP_UPDATE_INTERVAL_DEPRECATED,
|
|
CONF_IGNORE_UNAVAILABLE_STATE,
|
|
CONF_INCLUDE,
|
|
CONF_INCLUDE_NON_POWERCALC_SENSORS,
|
|
CONF_POWER_SENSOR_CATEGORY,
|
|
CONF_POWER_SENSOR_FRIENDLY_NAMING,
|
|
CONF_POWER_SENSOR_NAMING,
|
|
CONF_POWER_SENSOR_PRECISION,
|
|
CONF_POWER_UPDATE_INTERVAL,
|
|
CONF_SENSOR_TYPE,
|
|
CONF_SENSORS,
|
|
CONF_UNAVAILABLE_POWER,
|
|
CONF_UTILITY_METER_OFFSET,
|
|
CONF_UTILITY_METER_TARIFFS,
|
|
CONF_UTILITY_METER_TYPES,
|
|
DATA_ANALYTICS,
|
|
DATA_CONFIGURED_ENTITIES,
|
|
DATA_DISCOVERY_MANAGER,
|
|
DATA_DOMAIN_ENTITIES,
|
|
DATA_ENTITIES,
|
|
DATA_GROUP_ENTITIES,
|
|
DATA_STANDBY_POWER_SENSORS,
|
|
DATA_USED_UNIQUE_IDS,
|
|
DISCOVERY_TYPE,
|
|
DOMAIN,
|
|
DOMAIN_CONFIG,
|
|
ENERGY_INTEGRATION_METHODS,
|
|
ENTITY_CATEGORIES,
|
|
ENTRY_GLOBAL_CONFIG_UNIQUE_ID,
|
|
MIN_HA_VERSION,
|
|
SERVICE_CHANGE_GUI_CONFIGURATION,
|
|
SERVICE_RELOAD,
|
|
SERVICE_UPDATE_LIBRARY,
|
|
PowercalcDiscoveryType,
|
|
SensorType,
|
|
UnitPrefix,
|
|
)
|
|
from .discovery import DiscoveryManager, DiscoveryStatus
|
|
from .migrate import async_fix_legacy_profile_config_entry, async_migrate_config_entry
|
|
from .power_profile.power_profile import DeviceType
|
|
from .sensor import SENSOR_CONFIG
|
|
from .sensors.group.config_entry_utils import (
|
|
get_entries_excluding_global_config,
|
|
get_entries_having_subgroup,
|
|
remove_power_sensor_from_associated_groups,
|
|
)
|
|
from .service.gui_configuration import SERVICE_SCHEMA, change_gui_configuration
|
|
|
|
PLATFORMS = [Platform.SENSOR, Platform.SELECT]
|
|
|
|
DISCOVERY_SCHEMA = vol.Schema(
|
|
{
|
|
vol.Optional(CONF_ENABLED): cv.boolean,
|
|
vol.Optional(CONF_EXCLUDE_DEVICE_TYPES): vol.All(
|
|
cv.ensure_list,
|
|
[cls.value for cls in DeviceType],
|
|
),
|
|
vol.Optional(CONF_EXCLUDE_SELF_USAGE): cv.boolean,
|
|
},
|
|
)
|
|
CONFIG_SCHEMA = vol.Schema(
|
|
{
|
|
vol.Optional(DOMAIN, default=dict): vol.All(
|
|
cv.deprecated(CONF_DISCOVERY_EXCLUDE_DEVICE_TYPES_DEPRECATED),
|
|
cv.deprecated(CONF_DISCOVERY_EXCLUDE_SELF_USAGE_DEPRECATED),
|
|
cv.deprecated(CONF_ENABLE_AUTODISCOVERY_DEPRECATED),
|
|
cv.deprecated(CONF_GROUP_UPDATE_INTERVAL_DEPRECATED),
|
|
cv.deprecated(CONF_FORCE_UPDATE_FREQUENCY_DEPRECATED),
|
|
vol.Schema(
|
|
{
|
|
vol.Optional(CONF_ENABLE_ANALYTICS): cv.boolean,
|
|
vol.Optional(
|
|
CONF_FORCE_UPDATE_FREQUENCY_DEPRECATED,
|
|
): cv.time_period,
|
|
vol.Optional(CONF_GROUP_UPDATE_INTERVAL_DEPRECATED): cv.positive_int,
|
|
vol.Optional(CONF_GROUP_POWER_UPDATE_INTERVAL): cv.positive_int,
|
|
vol.Optional(CONF_GROUP_ENERGY_UPDATE_INTERVAL): cv.positive_int,
|
|
vol.Optional(CONF_ENERGY_UPDATE_INTERVAL): cv.positive_int,
|
|
vol.Optional(CONF_POWER_UPDATE_INTERVAL): cv.positive_int,
|
|
vol.Optional(CONF_POWER_SENSOR_NAMING): validate_name_pattern,
|
|
vol.Optional(CONF_POWER_SENSOR_FRIENDLY_NAMING): validate_name_pattern,
|
|
vol.Optional(CONF_POWER_SENSOR_CATEGORY): vol.In(ENTITY_CATEGORIES),
|
|
vol.Optional(CONF_ENERGY_SENSOR_NAMING): validate_name_pattern,
|
|
vol.Optional(CONF_ENERGY_SENSOR_FRIENDLY_NAMING): validate_name_pattern,
|
|
vol.Optional(CONF_ENERGY_SENSOR_CATEGORY): vol.In(ENTITY_CATEGORIES),
|
|
vol.Optional(CONF_DISABLE_EXTENDED_ATTRIBUTES): cv.boolean,
|
|
vol.Optional(CONF_DISABLE_LIBRARY_DOWNLOAD): cv.boolean,
|
|
vol.Optional(CONF_DISCOVERY): DISCOVERY_SCHEMA,
|
|
vol.Optional(CONF_DISCOVERY_EXCLUDE_DEVICE_TYPES_DEPRECATED): vol.All(
|
|
cv.ensure_list,
|
|
[cls.value for cls in DeviceType],
|
|
),
|
|
vol.Optional(CONF_ENABLE_AUTODISCOVERY_DEPRECATED): cv.boolean,
|
|
vol.Optional(CONF_DISCOVERY_EXCLUDE_SELF_USAGE_DEPRECATED): cv.boolean,
|
|
vol.Optional(CONF_CREATE_ENERGY_SENSORS): cv.boolean,
|
|
vol.Optional(CONF_CREATE_UTILITY_METERS): cv.boolean,
|
|
vol.Optional(CONF_UTILITY_METER_TARIFFS): vol.All(cv.ensure_list, [cv.string]),
|
|
vol.Optional(CONF_UTILITY_METER_TYPES): vol.All(cv.ensure_list, [vol.In(METER_TYPES)]),
|
|
vol.Optional(
|
|
CONF_UTILITY_METER_OFFSET,
|
|
): vol.All(cv.time_period, cv.positive_timedelta, max_28_days),
|
|
vol.Optional(CONF_ENERGY_INTEGRATION_METHOD): vol.In(ENERGY_INTEGRATION_METHODS),
|
|
vol.Optional(CONF_ENERGY_SENSOR_PRECISION): cv.positive_int,
|
|
vol.Optional(CONF_POWER_SENSOR_PRECISION): cv.positive_int,
|
|
vol.Optional(CONF_ENERGY_SENSOR_UNIT_PREFIX): vol.In([cls.value for cls in UnitPrefix]),
|
|
vol.Optional(CONF_CREATE_DOMAIN_GROUPS): vol.All(cv.ensure_list, [cv.string]),
|
|
vol.Optional(CONF_IGNORE_UNAVAILABLE_STATE): cv.boolean,
|
|
vol.Optional(CONF_UNAVAILABLE_POWER): vol.Coerce(float),
|
|
vol.Optional(CONF_SENSORS): vol.All(cv.ensure_list, [SENSOR_CONFIG]),
|
|
vol.Optional(CONF_INCLUDE_NON_POWERCALC_SENSORS): cv.boolean,
|
|
vol.Optional(CONF_CREATE_STANDBY_GROUP): cv.boolean,
|
|
},
|
|
),
|
|
),
|
|
},
|
|
extra=vol.ALLOW_EXTRA,
|
|
)
|
|
|
|
_LOGGER = logging.getLogger(__name__)
|
|
|
|
|
|
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
|
|
if AwesomeVersion(HA_VERSION) < AwesomeVersion(MIN_HA_VERSION): # pragma: no cover
|
|
msg = (
|
|
"This integration requires at least HomeAssistant version "
|
|
f" {MIN_HA_VERSION}, you are running version {HA_VERSION}."
|
|
" Please upgrade HomeAssistant to continue use this integration."
|
|
)
|
|
_notify_message(hass, "inv_ha_version", "PowerCalc", msg)
|
|
_LOGGER.critical(msg)
|
|
return False
|
|
|
|
global_config = get_global_configuration(hass, config)
|
|
|
|
discovery_manager = await create_discovery_manager_instance(hass, config, global_config)
|
|
hass.data[DOMAIN] = {
|
|
DATA_DISCOVERY_MANAGER: discovery_manager,
|
|
DOMAIN_CONFIG: global_config,
|
|
DATA_CONFIGURED_ENTITIES: {},
|
|
DATA_DOMAIN_ENTITIES: {},
|
|
DATA_GROUP_ENTITIES: {},
|
|
DATA_ENTITIES: {},
|
|
DATA_USED_UNIQUE_IDS: [],
|
|
DATA_STANDBY_POWER_SENSORS: {},
|
|
DATA_ANALYTICS: {},
|
|
}
|
|
|
|
register_services(hass)
|
|
|
|
await async_load_platform(hass, Platform.SELECT, DOMAIN, {}, config)
|
|
await setup_yaml_sensors(hass, config, global_config)
|
|
|
|
setup_domain_groups(hass, global_config)
|
|
setup_standby_group(hass, global_config)
|
|
|
|
try:
|
|
await repair_none_config_entries_issue(hass)
|
|
except Exception as e: # pragma: no cover
|
|
_LOGGER.error("problem while cleaning up None entities", exc_info=e) # pragma: no cover
|
|
|
|
await init_analytics(hass)
|
|
|
|
return True
|
|
|
|
|
|
async def init_analytics(hass: HomeAssistant) -> None:
|
|
"""Initialize the Analytics manager and schedule daily submission"""
|
|
analytics = Analytics(hass)
|
|
await analytics.load()
|
|
|
|
@callback
|
|
def start_schedule(_event: Event) -> None:
|
|
"""Start the send schedule after the started event."""
|
|
async_call_later(
|
|
hass,
|
|
600,
|
|
HassJob(
|
|
analytics.send_analytics,
|
|
name="powercalc analytics startup",
|
|
cancel_on_shutdown=True,
|
|
),
|
|
)
|
|
|
|
async_track_time_interval(
|
|
hass,
|
|
analytics.send_analytics,
|
|
ANALYTICS_INTERVAL,
|
|
name="powercalc analytics daily",
|
|
cancel_on_shutdown=True,
|
|
)
|
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, start_schedule)
|
|
|
|
|
|
async def create_discovery_manager_instance(
|
|
hass: HomeAssistant,
|
|
ha_config: ConfigType,
|
|
global_powercalc_config: ConfigType,
|
|
) -> DiscoveryManager:
|
|
discovery_config = global_powercalc_config.get(CONF_DISCOVERY, {})
|
|
exclude_device_types = [
|
|
DeviceType(device_type) for device_type in discovery_config.get(CONF_EXCLUDE_DEVICE_TYPES, [])
|
|
]
|
|
exclude_self_usage = discovery_config.get(CONF_EXCLUDE_SELF_USAGE, False)
|
|
enable_autodiscovery = discovery_config.get(CONF_ENABLED, True)
|
|
|
|
manager = DiscoveryManager(
|
|
hass,
|
|
ha_config,
|
|
exclude_device_types=exclude_device_types,
|
|
exclude_self_usage_profiles=exclude_self_usage,
|
|
enabled=enable_autodiscovery,
|
|
)
|
|
await manager.setup()
|
|
return manager
|
|
|
|
|
|
def register_services(hass: HomeAssistant) -> None:
|
|
"""Register generic services"""
|
|
|
|
async def _handle_change_gui_service(call: ServiceCall) -> None:
|
|
await change_gui_configuration(hass, call)
|
|
|
|
hass.services.async_register(
|
|
DOMAIN,
|
|
SERVICE_CHANGE_GUI_CONFIGURATION,
|
|
_handle_change_gui_service,
|
|
schema=SERVICE_SCHEMA,
|
|
)
|
|
|
|
async def _handle_update_library_service(_: ServiceCall) -> None:
|
|
_LOGGER.info("Updating library and rediscovering devices")
|
|
discovery_manager: DiscoveryManager = hass.data[DOMAIN][DATA_DISCOVERY_MANAGER]
|
|
await discovery_manager.update_library_and_rediscover()
|
|
|
|
hass.services.async_register(
|
|
DOMAIN,
|
|
SERVICE_UPDATE_LIBRARY,
|
|
_handle_update_library_service,
|
|
)
|
|
|
|
async def _reload_config(_: ServiceCall) -> None:
|
|
"""Reload powercalc."""
|
|
reload_config = await async_integration_yaml_config(hass, DOMAIN)
|
|
reset_platforms = async_get_platforms(hass, DOMAIN)
|
|
for reset_platform in reset_platforms:
|
|
await reset_platform.async_reset()
|
|
if not reload_config:
|
|
return # pragma: nocover
|
|
|
|
hass.data[DOMAIN][DATA_USED_UNIQUE_IDS] = []
|
|
hass.data[DOMAIN][DATA_CONFIGURED_ENTITIES] = {}
|
|
hass.data[DOMAIN][DATA_ANALYTICS] = {}
|
|
hass.data[DOMAIN][DOMAIN_CONFIG] = get_global_configuration(hass, reload_config)
|
|
|
|
# Reload YAML sensors if any
|
|
if DOMAIN in reload_config:
|
|
for sensor_config in reload_config[DOMAIN].get(CONF_SENSORS, []):
|
|
sensor_config.update({DISCOVERY_TYPE: PowercalcDiscoveryType.USER_YAML})
|
|
await async_load_platform(
|
|
hass,
|
|
Platform.SENSOR,
|
|
DOMAIN,
|
|
sensor_config,
|
|
reload_config,
|
|
)
|
|
|
|
# Reload all config entries
|
|
for entry in hass.config_entries.async_entries(DOMAIN):
|
|
_LOGGER.debug("Reloading config entry %s", entry.entry_id)
|
|
await hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
global_config = get_global_configuration(hass, reload_config)
|
|
setup_domain_groups(hass, global_config)
|
|
await create_standby_group(hass, global_config)
|
|
|
|
hass.services.async_register(
|
|
DOMAIN,
|
|
SERVICE_RELOAD,
|
|
_reload_config,
|
|
)
|
|
|
|
|
|
async def create_standby_group(
|
|
hass: HomeAssistant,
|
|
domain_config: ConfigType,
|
|
event: Event[Any] | None = None,
|
|
) -> None:
|
|
if not bool(domain_config.get(CONF_CREATE_STANDBY_GROUP, True)):
|
|
return
|
|
hass.async_create_task(
|
|
async_load_platform(
|
|
hass,
|
|
SENSOR_DOMAIN,
|
|
DOMAIN,
|
|
{DISCOVERY_TYPE: PowercalcDiscoveryType.STANDBY_GROUP},
|
|
domain_config,
|
|
),
|
|
)
|
|
|
|
|
|
def setup_standby_group(hass: HomeAssistant, domain_config: ConfigType) -> None:
|
|
hass.bus.async_listen_once(
|
|
EVENT_HOMEASSISTANT_STARTED,
|
|
partial(create_standby_group, hass, domain_config),
|
|
)
|
|
|
|
|
|
def setup_domain_groups(hass: HomeAssistant, global_config: ConfigType) -> None:
|
|
domain_groups: list[str] | None = global_config.get(CONF_CREATE_DOMAIN_GROUPS)
|
|
if not domain_groups:
|
|
return
|
|
|
|
_LOGGER.debug("Setting up domain based group sensors..")
|
|
for domain in domain_groups:
|
|
hass.async_create_task(
|
|
async_load_platform(
|
|
hass,
|
|
SENSOR_DOMAIN,
|
|
DOMAIN,
|
|
{
|
|
DISCOVERY_TYPE: PowercalcDiscoveryType.DOMAIN_GROUP,
|
|
CONF_DOMAIN: domain,
|
|
},
|
|
global_config,
|
|
),
|
|
)
|
|
|
|
|
|
async def setup_yaml_sensors(
|
|
hass: HomeAssistant,
|
|
config: ConfigType,
|
|
domain_config: ConfigType,
|
|
) -> None:
|
|
sensors: list = domain_config.get(CONF_SENSORS, [])
|
|
primary_sensors = []
|
|
secondary_sensors = []
|
|
|
|
for sensor_config in sensors:
|
|
sensor_config.update({DISCOVERY_TYPE: PowercalcDiscoveryType.USER_YAML})
|
|
|
|
if CONF_INCLUDE in sensor_config:
|
|
secondary_sensors.append(sensor_config)
|
|
else:
|
|
primary_sensors.append(sensor_config)
|
|
|
|
async def _load_secondary_sensors(_: None) -> None:
|
|
"""Load secondary sensors after primary sensors."""
|
|
await asyncio.gather(
|
|
*(
|
|
hass.async_create_task(
|
|
async_load_platform(
|
|
hass,
|
|
Platform.SENSOR,
|
|
DOMAIN,
|
|
sensor_config,
|
|
config,
|
|
),
|
|
)
|
|
for sensor_config in secondary_sensors
|
|
),
|
|
)
|
|
|
|
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STARTED, _load_secondary_sensors)
|
|
|
|
await asyncio.gather(
|
|
*(
|
|
hass.async_create_task(
|
|
async_load_platform(
|
|
hass,
|
|
Platform.SENSOR,
|
|
DOMAIN,
|
|
sensor_config,
|
|
config,
|
|
),
|
|
)
|
|
for sensor_config in primary_sensors
|
|
),
|
|
)
|
|
|
|
|
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|
"""Set up Powercalc integration from a config entry."""
|
|
|
|
await async_fix_legacy_profile_config_entry(hass, entry)
|
|
await hass.config_entries.async_forward_entry_setups(entry, [Platform.SENSOR, Platform.SELECT])
|
|
# await hass.config_entries.async_forward_entry_setups(entry, [Platform.SENSOR])
|
|
|
|
entry.async_on_unload(entry.add_update_listener(async_update_entry))
|
|
|
|
# Check if this is the initial creation of the global configuration entry
|
|
# If so, update the global configuration with the GUI configuration
|
|
# When the flag is set, the global configuration has already been applied during async_setup
|
|
if entry.unique_id == ENTRY_GLOBAL_CONFIG_UNIQUE_ID:
|
|
global_config = hass.data[DOMAIN][DOMAIN_CONFIG]
|
|
if global_config.get(FLAG_HAS_GLOBAL_GUI_CONFIG, False) is False:
|
|
await apply_global_gui_configuration_changes(hass)
|
|
|
|
discovery_enabled = bool(entry.data.get(CONF_DISCOVERY, {}).get(CONF_ENABLED, False))
|
|
discovery_manager: DiscoveryManager = hass.data[DOMAIN][DATA_DISCOVERY_MANAGER]
|
|
if discovery_enabled and discovery_manager.status == DiscoveryStatus.DISABLED:
|
|
_LOGGER.debug("Enabling discovery manager based on global configuration")
|
|
discovery_manager.enable()
|
|
await discovery_manager.setup()
|
|
if not discovery_enabled and discovery_manager.status != DiscoveryStatus.DISABLED:
|
|
_LOGGER.debug("Disabling discovery manager based on global configuration")
|
|
await discovery_manager.disable()
|
|
|
|
return True
|
|
|
|
|
|
async def async_update_entry(hass: HomeAssistant, entry: ConfigEntry) -> None:
|
|
"""Update a given config entry."""
|
|
|
|
if entry.unique_id == ENTRY_GLOBAL_CONFIG_UNIQUE_ID:
|
|
await apply_global_gui_configuration_changes(hass)
|
|
|
|
await hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
# Also reload all "parent" groups referring this group as a subgroup
|
|
for related_entry in get_entries_having_subgroup(hass, entry):
|
|
await hass.config_entries.async_reload(related_entry.entry_id)
|
|
|
|
|
|
async def apply_global_gui_configuration_changes(hass: HomeAssistant) -> None:
|
|
"""Apply global configuration changes to all entities."""
|
|
global_config = hass.data[DOMAIN][DOMAIN_CONFIG]
|
|
global_config.clear()
|
|
global_config.update(get_global_configuration(hass, {}))
|
|
for entry in get_entries_excluding_global_config(hass):
|
|
if entry.state != ConfigEntryState.LOADED: # pragma: no cover
|
|
continue
|
|
await hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
|
|
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
|
"""Unload a config entry."""
|
|
unload_ok = await hass.config_entries.async_unload_platforms(
|
|
config_entry,
|
|
PLATFORMS,
|
|
)
|
|
|
|
if unload_ok:
|
|
used_unique_ids: list[str] = hass.data[DOMAIN][DATA_USED_UNIQUE_IDS]
|
|
try:
|
|
if config_entry.unique_id:
|
|
used_unique_ids.remove(config_entry.unique_id)
|
|
except ValueError:
|
|
return True
|
|
|
|
return unload_ok
|
|
|
|
|
|
async def async_remove_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
|
"""Called after a config entry is removed."""
|
|
discovery_manager: DiscoveryManager = hass.data[DOMAIN][DATA_DISCOVERY_MANAGER]
|
|
discovery_manager.remove_initialized_flow(config_entry)
|
|
|
|
updated_entries: list[ConfigEntry] = []
|
|
|
|
sensor_type = config_entry.data.get(CONF_SENSOR_TYPE)
|
|
if sensor_type == SensorType.VIRTUAL_POWER:
|
|
updated_entries = remove_power_sensor_from_associated_groups(
|
|
hass,
|
|
config_entry,
|
|
)
|
|
|
|
for entry in updated_entries:
|
|
if entry.state == ConfigEntryState.LOADED:
|
|
await hass.config_entries.async_reload(entry.entry_id)
|
|
|
|
|
|
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
|
|
"""Migrate old entry."""
|
|
await async_migrate_config_entry(hass, config_entry)
|
|
return True
|
|
|
|
|
|
async def repair_none_config_entries_issue(hass: HomeAssistant) -> None:
|
|
"""Repair issue with config entries having None as data."""
|
|
entity_registry = er.async_get(hass)
|
|
entries = [entry for entry in hass.config_entries.async_entries(DOMAIN) if entry.title == "None"]
|
|
for entry in entries:
|
|
_LOGGER.debug("Removing entry %s with None data", entry.entry_id)
|
|
entities = entity_registry.entities.get_entries_for_config_entry_id(entry.entry_id)
|
|
for entity in entities:
|
|
entity_registry.async_remove(entity.entity_id)
|
|
try:
|
|
unique_id = f"{int(time.time() * 1000)}_{random.randint(1000, 9999)}" # noqa: S311
|
|
object.__setattr__(entry, "unique_id", unique_id)
|
|
hass.config_entries._entries._index_entry(entry) # noqa: SLF001
|
|
await hass.config_entries.async_remove(entry.entry_id)
|
|
except Exception as e: # pragma: no cover
|
|
_LOGGER.error("problem while cleaning up None entities", exc_info=e) # pragma: no cover
|
|
|
|
|
|
def _notify_message(
|
|
hass: HomeAssistant,
|
|
notification_id: str,
|
|
title: str,
|
|
message: str,
|
|
) -> None: # pragma: no cover
|
|
"""Notify user with persistent notification."""
|
|
hass.async_create_task(
|
|
hass.services.async_call(
|
|
domain="persistent_notification",
|
|
service="create",
|
|
service_data={
|
|
"title": title,
|
|
"message": message,
|
|
"notification_id": f"{DOMAIN}.{notification_id}",
|
|
},
|
|
),
|
|
)
|