Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reload Magic Areas entry when entity registry is updated #422

Merged
merged 2 commits into from
Oct 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
213 changes: 131 additions & 82 deletions custom_components/magic_areas/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
"""Magic Areas component for Home Assistant."""

from collections import defaultdict
from datetime import UTC, datetime
import logging

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_NAME
from homeassistant.core import HomeAssistant
from homeassistant.const import ATTR_NAME, EVENT_HOMEASSISTANT_STARTED
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers.area_registry import async_get as areareg_async_get
from homeassistant.helpers.entity_registry import (
EVENT_ENTITY_REGISTRY_UPDATED,
EventEntityRegistryUpdatedData,
)
from homeassistant.helpers.floor_registry import async_get as floorreg_async_get

from .base.magic import MagicArea, MagicMetaArea
Expand All @@ -19,6 +24,7 @@
CONF_SECONDARY_STATES,
CONF_SLEEP_TIMEOUT,
DATA_AREA_OBJECT,
DATA_ENTITY_LISTENER,
DATA_UNDO_UPDATE_LISTENER,
DEFAULT_CLEAR_TIMEOUT,
DEFAULT_EXTENDED_TIME,
Expand All @@ -41,103 +47,139 @@
_LOGGER = logging.getLogger(__name__)


async def async_setup(hass: HomeAssistant, config: dict):
"""Set up the component."""
return True


async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry):
"""Set up the component."""
data = hass.data.setdefault(MODULE_DATA, {})
area_id = config_entry.data[CONF_ID]
area_name = config_entry.data[CONF_NAME]

_LOGGER.debug("%s: Setting up entry.", area_name)

# Load floors
floor_registry = floorreg_async_get(hass)
floors = floor_registry.async_list_floors()

non_floor_meta_ids = [
meta_area_type
for meta_area_type in MetaAreaType
if meta_area_type != MetaAreaType.FLOOR
]
floor_ids = [f.floor_id for f in floors]

if area_id in non_floor_meta_ids:
meta_area = basic_area_from_meta(area_id)
magic_area = MagicMetaArea(hass, meta_area, config_entry)
elif area_id in floor_ids:
meta_area = basic_area_from_floor(floor_registry.async_get_floor(area_id))
magic_area = MagicMetaArea(hass, meta_area, config_entry)
else:
area_registry = areareg_async_get(hass)
area = area_registry.async_get_area(area_id)

if not area:
_LOGGER.warning("%s: ID '%s' not found on registry", area_name, area_id)
return False

_LOGGER.debug("%s: Got area from registry: %s", area_name, str(area))

magic_area = MagicArea(
hass,
basic_area_from_object(area),
@callback
def _async_registry_updated(event: Event[EventEntityRegistryUpdatedData]) -> None:
"""Reload integration when entity registry is updated."""
_LOGGER.debug(
"%s: Reloading entry due entity registry change",
config_entry.data[CONF_NAME],
)
hass.config_entries.async_update_entry(
config_entry,
data={**config_entry.data, "entity_ts": datetime.now(UTC)},
)

_LOGGER.debug(
"%s: Magic Area (%s) created: %s",
magic_area.name,
magic_area.id,
str(magic_area.config),
)
async def _async_setup_integration(*args, **kwargs) -> None:
"""Load integration when Hass has finished starting."""

_LOGGER.debug("%s: Setting up entry.", area_name)

# Load floors
floor_registry = floorreg_async_get(hass)
floors = floor_registry.async_list_floors()

non_floor_meta_ids = [
meta_area_type
for meta_area_type in MetaAreaType
if meta_area_type != MetaAreaType.FLOOR
]
floor_ids = [f.floor_id for f in floors]

if area_id in non_floor_meta_ids:
meta_area = basic_area_from_meta(area_id)
magic_area = MagicMetaArea(hass, meta_area, config_entry)
elif area_id in floor_ids:
meta_area = basic_area_from_floor(floor_registry.async_get_floor(area_id))
magic_area = MagicMetaArea(hass, meta_area, config_entry)
else:
area_registry = areareg_async_get(hass)
area = area_registry.async_get_area(area_id)

if not area:
_LOGGER.warning("%s: ID '%s' not found on registry", area_name, area_id)
return False

_LOGGER.debug("%s: Got area from registry: %s", area_name, str(area))

magic_area = MagicArea(
hass,
basic_area_from_object(area),
config_entry,
)

_LOGGER.debug(
"%s: Magic Area (%s) created: %s",
magic_area.name,
magic_area.id,
str(magic_area.config),
)

undo_listener = config_entry.add_update_listener(async_update_options)
undo_listener = config_entry.add_update_listener(async_update_options)

data[config_entry.entry_id] = {
DATA_AREA_OBJECT: magic_area,
DATA_UNDO_UPDATE_LISTENER: undo_listener,
}
# Watch for area changes.
entity_listener = hass.bus.async_listen(
EVENT_ENTITY_REGISTRY_UPDATED,
_async_registry_updated,
_entity_registry_filter,
)

# Setup platforms
await hass.config_entries.async_forward_entry_setups(
config_entry, magic_area.available_platforms()
)
data[config_entry.entry_id] = {
DATA_AREA_OBJECT: magic_area,
DATA_UNDO_UPDATE_LISTENER: undo_listener,
DATA_ENTITY_LISTENER: entity_listener,
}

# Conditional reload of related meta-areas
# Populate dict with all meta-areas with ID as key
meta_areas = defaultdict()

for area in data.values():
area_obj = area[DATA_AREA_OBJECT]
if area_obj.is_meta():
meta_areas[area_obj.id] = area_obj

# Handle non-meta areas
if not magic_area.is_meta():
meta_area_key = (
META_AREA_EXTERIOR.lower()
if magic_area.is_exterior()
else META_AREA_INTERIOR.lower()
# Setup platforms
await hass.config_entries.async_forward_entry_setups(
config_entry, magic_area.available_platforms()
)

if meta_area_key in meta_areas:
meta_area_object = meta_areas[meta_area_key]
# Conditional reload of related meta-areas
# Populate dict with all meta-areas with ID as key
meta_areas = defaultdict()

for area in data.values():
area_obj = area[DATA_AREA_OBJECT]
if area_obj.is_meta():
meta_areas[area_obj.id] = area_obj

# Handle non-meta areas
if not magic_area.is_meta():
meta_area_key = (
META_AREA_EXTERIOR.lower()
if magic_area.is_exterior()
else META_AREA_INTERIOR.lower()
)

if meta_area_key in meta_areas:
meta_area_object = meta_areas[meta_area_key]

if meta_area_object.initialized:
await hass.config_entries.async_reload(
meta_area_object.hass_config.entry_id
)
else:
meta_area_global_id = META_AREA_GLOBAL.lower()

if (
magic_area.id != meta_area_global_id
and meta_area_global_id in meta_areas
):
if meta_areas[meta_area_global_id].initialized:
await hass.config_entries.async_reload(
meta_areas[meta_area_global_id].hass_config.entry_id
)

return True

hass.data.setdefault(MODULE_DATA, {})

if meta_area_object.initialized:
await hass.config_entries.async_reload(
meta_area_object.hass_config.entry_id
)
else:
meta_area_global_id = META_AREA_GLOBAL.lower()
area_id = config_entry.data[CONF_ID]
area_name = config_entry.data[CONF_NAME]

if magic_area.id != meta_area_global_id and meta_area_global_id in meta_areas:
if meta_areas[meta_area_global_id].initialized:
await hass.config_entries.async_reload(
meta_areas[meta_area_global_id].hass_config.entry_id
)
# Wait for Hass to have started before setting up.
if hass.is_running:
hass.create_task(_async_setup_integration())
else:
hass.bus.async_listen_once(
EVENT_HOMEASSISTANT_STARTED, _async_setup_integration
)

return True

Expand All @@ -162,6 +204,7 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
)

area_data[DATA_UNDO_UPDATE_LISTENER]()
area_data[DATA_ENTITY_LISTENER]()
data.pop(config_entry.entry_id)

if not data:
Expand Down Expand Up @@ -237,3 +280,9 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry):
)

return True


@callback
def _entity_registry_filter(event_data: EventEntityRegistryUpdatedData) -> bool:
"""Filter entity registry events."""
return event_data["action"] == "update" and "area_id" in event_data["changes"]
1 change: 1 addition & 0 deletions custom_components/magic_areas/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,7 @@ class MagicAreasEvents(StrEnum):
# Data Items
DATA_AREA_OBJECT = "area_object"
DATA_UNDO_UPDATE_LISTENER = "undo_update_listener"
DATA_ENTITY_LISTENER = "entity_registry_listener"

# Attributes
ATTR_STATES = "states"
Expand Down