Skip to content

Commit

Permalink
Reload Magic Areas entry when entity registry is updated (#422)
Browse files Browse the repository at this point in the history
* added callback to reload entry on registry change

* post-linter changes
  • Loading branch information
jseidl authored Oct 19, 2024
1 parent a1010a1 commit 261e4d9
Show file tree
Hide file tree
Showing 2 changed files with 132 additions and 82 deletions.
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

0 comments on commit 261e4d9

Please sign in to comment.