Purge entity and device registries when importing lcn from configuration.yaml (#54266)
* Identify LCN orphans in entity registry and device registry and remove them * Fix typing issues * Revert "Fix typing issues" This reverts commit eccd067b3b5f23135e6c8a79d25f7f2cbc2d0ae9. * Fix removal of devices which do not belong to given config_entry * Use helper for getting entities for config_entry * Rename variable
This commit is contained in:
parent
16088915eb
commit
ead6e7da1f
3 changed files with 81 additions and 11 deletions
|
@ -2,7 +2,6 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
import pypck
|
import pypck
|
||||||
|
|
||||||
|
@ -16,15 +15,16 @@ from homeassistant.const import (
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.data_entry_flow import FlowResult
|
from homeassistant.data_entry_flow import FlowResult
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN
|
from .const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN
|
||||||
|
from .helpers import purge_device_registry, purge_entity_registry
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_config_entry(
|
def get_config_entry(
|
||||||
hass: HomeAssistant, data: dict[str, Any]
|
hass: HomeAssistant, data: ConfigType
|
||||||
) -> config_entries.ConfigEntry | None:
|
) -> config_entries.ConfigEntry | None:
|
||||||
"""Check config entries for already configured entries based on the ip address/port."""
|
"""Check config entries for already configured entries based on the ip address/port."""
|
||||||
return next(
|
return next(
|
||||||
|
@ -38,7 +38,7 @@ def get_config_entry(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def validate_connection(host_name: str, data: dict[str, Any]) -> dict[str, Any]:
|
async def validate_connection(host_name: str, data: ConfigType) -> ConfigType:
|
||||||
"""Validate if a connection to LCN can be established."""
|
"""Validate if a connection to LCN can be established."""
|
||||||
host = data[CONF_IP_ADDRESS]
|
host = data[CONF_IP_ADDRESS]
|
||||||
port = data[CONF_PORT]
|
port = data[CONF_PORT]
|
||||||
|
@ -70,7 +70,7 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
|
|
||||||
VERSION = 1
|
VERSION = 1
|
||||||
|
|
||||||
async def async_step_import(self, data: dict[str, Any]) -> FlowResult:
|
async def async_step_import(self, data: ConfigType) -> FlowResult:
|
||||||
"""Import existing configuration from LCN."""
|
"""Import existing configuration from LCN."""
|
||||||
host_name = data[CONF_HOST]
|
host_name = data[CONF_HOST]
|
||||||
# validate the imported connection parameters
|
# validate the imported connection parameters
|
||||||
|
@ -93,13 +93,10 @@ class LcnFlowHandler(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
# check if we already have a host with the same address configured
|
# check if we already have a host with the same address configured
|
||||||
if entry := get_config_entry(self.hass, data):
|
if entry := get_config_entry(self.hass, data):
|
||||||
entry.source = config_entries.SOURCE_IMPORT
|
entry.source = config_entries.SOURCE_IMPORT
|
||||||
|
|
||||||
# Cleanup entity and device registry, if we imported from configuration.yaml to
|
# Cleanup entity and device registry, if we imported from configuration.yaml to
|
||||||
# remove orphans when entities were removed from configuration
|
# remove orphans when entities were removed from configuration
|
||||||
entity_registry = er.async_get(self.hass)
|
purge_entity_registry(self.hass, entry.entry_id, data)
|
||||||
entity_registry.async_clear_config_entry(entry.entry_id)
|
purge_device_registry(self.hass, entry.entry_id, data)
|
||||||
device_registry = dr.async_get(self.hass)
|
|
||||||
device_registry.async_clear_config_entry(entry.entry_id)
|
|
||||||
|
|
||||||
self.hass.config_entries.async_update_entry(entry, data=data)
|
self.hass.config_entries.async_update_entry(entry, data=data)
|
||||||
return self.async_abort(reason="existing_configuration_updated")
|
return self.async_abort(reason="existing_configuration_updated")
|
||||||
|
|
|
@ -30,7 +30,7 @@ from homeassistant.const import (
|
||||||
CONF_USERNAME,
|
CONF_USERNAME,
|
||||||
)
|
)
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import device_registry as dr
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
from homeassistant.helpers.typing import ConfigType
|
from homeassistant.helpers.typing import ConfigType
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
|
@ -247,6 +247,75 @@ def import_lcn_config(lcn_config: ConfigType) -> list[ConfigType]:
|
||||||
return list(data.values())
|
return list(data.values())
|
||||||
|
|
||||||
|
|
||||||
|
def purge_entity_registry(
|
||||||
|
hass: HomeAssistant, entry_id: str, imported_entry_data: ConfigType
|
||||||
|
) -> None:
|
||||||
|
"""Remove orphans from entity registry which are not in entry data."""
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
# Find all entities that are referenced in the config entry.
|
||||||
|
references_config_entry = {
|
||||||
|
entity_entry.entity_id
|
||||||
|
for entity_entry in er.async_entries_for_config_entry(entity_registry, entry_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find all entities that are referenced by the entry_data.
|
||||||
|
references_entry_data = set()
|
||||||
|
for entity_data in imported_entry_data[CONF_ENTITIES]:
|
||||||
|
entity_unique_id = generate_unique_id(
|
||||||
|
entry_id, entity_data[CONF_ADDRESS], entity_data[CONF_RESOURCE]
|
||||||
|
)
|
||||||
|
entity_id = entity_registry.async_get_entity_id(
|
||||||
|
entity_data[CONF_DOMAIN], DOMAIN, entity_unique_id
|
||||||
|
)
|
||||||
|
if entity_id is not None:
|
||||||
|
references_entry_data.add(entity_id)
|
||||||
|
|
||||||
|
orphaned_ids = references_config_entry - references_entry_data
|
||||||
|
for orphaned_id in orphaned_ids:
|
||||||
|
entity_registry.async_remove(orphaned_id)
|
||||||
|
|
||||||
|
|
||||||
|
def purge_device_registry(
|
||||||
|
hass: HomeAssistant, entry_id: str, imported_entry_data: ConfigType
|
||||||
|
) -> None:
|
||||||
|
"""Remove orphans from device registry which are not in entry data."""
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
entity_registry = er.async_get(hass)
|
||||||
|
|
||||||
|
# Find all devices that are referenced in the entity registry.
|
||||||
|
references_entities = {
|
||||||
|
entry.device_id for entry in entity_registry.entities.values()
|
||||||
|
}
|
||||||
|
|
||||||
|
# Find device that references the host.
|
||||||
|
references_host = set()
|
||||||
|
host_device = device_registry.async_get_device({(DOMAIN, entry_id)})
|
||||||
|
if host_device is not None:
|
||||||
|
references_host.add(host_device.id)
|
||||||
|
|
||||||
|
# Find all devices that are referenced by the entry_data.
|
||||||
|
references_entry_data = set()
|
||||||
|
for device_data in imported_entry_data[CONF_DEVICES]:
|
||||||
|
device_unique_id = generate_unique_id(entry_id, device_data[CONF_ADDRESS])
|
||||||
|
device = device_registry.async_get_device({(DOMAIN, device_unique_id)})
|
||||||
|
if device is not None:
|
||||||
|
references_entry_data.add(device.id)
|
||||||
|
|
||||||
|
orphaned_ids = (
|
||||||
|
{
|
||||||
|
entry.id
|
||||||
|
for entry in dr.async_entries_for_config_entry(device_registry, entry_id)
|
||||||
|
}
|
||||||
|
- references_entities
|
||||||
|
- references_host
|
||||||
|
- references_entry_data
|
||||||
|
)
|
||||||
|
|
||||||
|
for device_id in orphaned_ids:
|
||||||
|
device_registry.async_remove_device(device_id)
|
||||||
|
|
||||||
|
|
||||||
def register_lcn_host_device(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
def register_lcn_host_device(hass: HomeAssistant, config_entry: ConfigEntry) -> None:
|
||||||
"""Register LCN host for given config_entry in device registry."""
|
"""Register LCN host for given config_entry in device registry."""
|
||||||
device_registry = dr.async_get(hass)
|
device_registry = dr.async_get(hass)
|
||||||
|
|
|
@ -7,6 +7,8 @@ import pytest
|
||||||
from homeassistant import config_entries, data_entry_flow
|
from homeassistant import config_entries, data_entry_flow
|
||||||
from homeassistant.components.lcn.const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN
|
from homeassistant.components.lcn.const import CONF_DIM_MODE, CONF_SK_NUM_TRIES, DOMAIN
|
||||||
from homeassistant.const import (
|
from homeassistant.const import (
|
||||||
|
CONF_DEVICES,
|
||||||
|
CONF_ENTITIES,
|
||||||
CONF_HOST,
|
CONF_HOST,
|
||||||
CONF_IP_ADDRESS,
|
CONF_IP_ADDRESS,
|
||||||
CONF_PASSWORD,
|
CONF_PASSWORD,
|
||||||
|
@ -24,6 +26,8 @@ IMPORT_DATA = {
|
||||||
CONF_PASSWORD: "lcn",
|
CONF_PASSWORD: "lcn",
|
||||||
CONF_SK_NUM_TRIES: 0,
|
CONF_SK_NUM_TRIES: 0,
|
||||||
CONF_DIM_MODE: "STEPS200",
|
CONF_DIM_MODE: "STEPS200",
|
||||||
|
CONF_DEVICES: [],
|
||||||
|
CONF_ENTITIES: [],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue