mirror of
https://github.com/home-assistant/core.git
synced 2025-07-23 21:27:38 +00:00
Cleanup device registry for tedee when a lock is removed (#106994)
* remove removed locks * move duplicated code to function * remove entities by removing device * add new locks automatically * add locks from coordinator * smaller pr * remove snapshot * move lock removal to coordinator * change comment * Update tests/components/tedee/test_init.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update tests/components/tedee/test_init.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * test lock unavailable * move logic to function * resolve merge conflicts * no need to call keys() * no need to call keys() * check for change first * readability * Update tests/components/tedee/test_lock.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> * Update tests/components/tedee/test_lock.py Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com> --------- Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
parent
5fe96390f5
commit
14bf778c10
@ -18,6 +18,7 @@ from homeassistant.const import CONF_HOST
|
|||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.exceptions import ConfigEntryAuthFailed
|
from homeassistant.exceptions import ConfigEntryAuthFailed
|
||||||
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
from homeassistant.helpers.aiohttp_client import async_get_clientsession
|
||||||
|
import homeassistant.helpers.device_registry as dr
|
||||||
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
|
||||||
|
|
||||||
from .const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN
|
from .const import CONF_LOCAL_ACCESS_TOKEN, DOMAIN
|
||||||
@ -50,7 +51,7 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
|
|||||||
)
|
)
|
||||||
|
|
||||||
self._next_get_locks = time.time()
|
self._next_get_locks = time.time()
|
||||||
self._current_locks: set[int] = set()
|
self._locks_last_update: set[int] = set()
|
||||||
self.new_lock_callbacks: list[Callable[[int], None]] = []
|
self.new_lock_callbacks: list[Callable[[int], None]] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -84,15 +85,7 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
|
|||||||
", ".join(map(str, self.tedee_client.locks_dict.keys())),
|
", ".join(map(str, self.tedee_client.locks_dict.keys())),
|
||||||
)
|
)
|
||||||
|
|
||||||
if not self._current_locks:
|
self._async_add_remove_locks()
|
||||||
self._current_locks = set(self.tedee_client.locks_dict.keys())
|
|
||||||
|
|
||||||
if new_locks := set(self.tedee_client.locks_dict.keys()) - self._current_locks:
|
|
||||||
_LOGGER.debug("New locks found: %s", ", ".join(map(str, new_locks)))
|
|
||||||
for lock_id in new_locks:
|
|
||||||
for callback in self.new_lock_callbacks:
|
|
||||||
callback(lock_id)
|
|
||||||
|
|
||||||
return self.tedee_client.locks_dict
|
return self.tedee_client.locks_dict
|
||||||
|
|
||||||
async def _async_update(self, update_fn: Callable[[], Awaitable[None]]) -> None:
|
async def _async_update(self, update_fn: Callable[[], Awaitable[None]]) -> None:
|
||||||
@ -109,3 +102,32 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
|
|||||||
raise UpdateFailed("Error while updating data: %s" % str(ex)) from ex
|
raise UpdateFailed("Error while updating data: %s" % str(ex)) from ex
|
||||||
except (TedeeClientException, TimeoutError) as ex:
|
except (TedeeClientException, TimeoutError) as ex:
|
||||||
raise UpdateFailed("Querying API failed. Error: %s" % str(ex)) from ex
|
raise UpdateFailed("Querying API failed. Error: %s" % str(ex)) from ex
|
||||||
|
|
||||||
|
def _async_add_remove_locks(self) -> None:
|
||||||
|
"""Add new locks, remove non-existing locks."""
|
||||||
|
if not self._locks_last_update:
|
||||||
|
self._locks_last_update = set(self.tedee_client.locks_dict)
|
||||||
|
|
||||||
|
if (
|
||||||
|
current_locks := set(self.tedee_client.locks_dict)
|
||||||
|
) == self._locks_last_update:
|
||||||
|
return
|
||||||
|
|
||||||
|
# remove old locks
|
||||||
|
if removed_locks := self._locks_last_update - current_locks:
|
||||||
|
_LOGGER.debug("Removed locks: %s", ", ".join(map(str, removed_locks)))
|
||||||
|
device_registry = dr.async_get(self.hass)
|
||||||
|
for lock_id in removed_locks:
|
||||||
|
if device := device_registry.async_get_device(
|
||||||
|
identifiers={(DOMAIN, str(lock_id))}
|
||||||
|
):
|
||||||
|
device_registry.async_remove_device(device.id)
|
||||||
|
|
||||||
|
# add new locks
|
||||||
|
if new_locks := current_locks - self._locks_last_update:
|
||||||
|
_LOGGER.debug("New locks found: %s", ", ".join(map(str, new_locks)))
|
||||||
|
for lock_id in new_locks:
|
||||||
|
for callback in self.new_lock_callbacks:
|
||||||
|
callback(lock_id)
|
||||||
|
|
||||||
|
self._locks_last_update = current_locks
|
||||||
|
@ -38,9 +38,14 @@ class TedeeEntity(CoordinatorEntity[TedeeApiCoordinator]):
|
|||||||
@callback
|
@callback
|
||||||
def _handle_coordinator_update(self) -> None:
|
def _handle_coordinator_update(self) -> None:
|
||||||
"""Handle updated data from the coordinator."""
|
"""Handle updated data from the coordinator."""
|
||||||
self._lock = self.coordinator.data[self._lock.lock_id]
|
self._lock = self.coordinator.data.get(self._lock.lock_id, self._lock)
|
||||||
super()._handle_coordinator_update()
|
super()._handle_coordinator_update()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def available(self) -> bool:
|
||||||
|
"""Return True if entity is available."""
|
||||||
|
return super().available and self._lock.is_connected
|
||||||
|
|
||||||
|
|
||||||
class TedeeDescriptionEntity(TedeeEntity):
|
class TedeeDescriptionEntity(TedeeEntity):
|
||||||
"""Base class for Tedee device entities."""
|
"""Base class for Tedee device entities."""
|
||||||
|
@ -74,11 +74,6 @@ class TedeeLockEntity(TedeeEntity, LockEntity):
|
|||||||
"""Return true if lock is jammed."""
|
"""Return true if lock is jammed."""
|
||||||
return self._lock.is_state_jammed
|
return self._lock.is_state_jammed
|
||||||
|
|
||||||
@property
|
|
||||||
def available(self) -> bool:
|
|
||||||
"""Return True if entity is available."""
|
|
||||||
return super().available and self._lock.is_connected
|
|
||||||
|
|
||||||
async def async_unlock(self, **kwargs: Any) -> None:
|
async def async_unlock(self, **kwargs: Any) -> None:
|
||||||
"""Unlock the door."""
|
"""Unlock the door."""
|
||||||
try:
|
try:
|
||||||
|
@ -25,7 +25,7 @@ from homeassistant.core import HomeAssistant
|
|||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
from homeassistant.helpers import device_registry as dr, entity_registry as er
|
||||||
|
|
||||||
from tests.common import async_fire_time_changed
|
from tests.common import MockConfigEntry, async_fire_time_changed
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
pytestmark = pytest.mark.usefixtures("init_integration")
|
||||||
|
|
||||||
@ -210,6 +210,36 @@ async def test_update_failed(
|
|||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_cleanup_removed_locks(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_tedee: MagicMock,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure removed locks are cleaned up."""
|
||||||
|
|
||||||
|
devices = dr.async_entries_for_config_entry(
|
||||||
|
device_registry, mock_config_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
locks = [device.name for device in devices]
|
||||||
|
assert "Lock-1A2B" in locks
|
||||||
|
|
||||||
|
# remove a lock and wait for coordinator
|
||||||
|
mock_tedee.locks_dict.pop(12345)
|
||||||
|
freezer.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
devices = dr.async_entries_for_config_entry(
|
||||||
|
device_registry, mock_config_entry.entry_id
|
||||||
|
)
|
||||||
|
|
||||||
|
locks = [device.name for device in devices]
|
||||||
|
assert "Lock-1A2B" not in locks
|
||||||
|
|
||||||
|
|
||||||
async def test_new_lock(
|
async def test_new_lock(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
mock_tedee: MagicMock,
|
mock_tedee: MagicMock,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user