Add new locks automatically to tedee integration (#107372)
* remove removed locks * move duplicated code to function * remove entities by removing device * add new locks automatically * add locks from coordinator * remove other PR stuff * add pullspring lock to test for coverage * requested changes
This commit is contained in:
parent
d6aaaf1f1a
commit
7385da626e
9 changed files with 127 additions and 2 deletions
|
@ -66,6 +66,17 @@ async def async_setup_entry(
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _async_add_new_lock(lock_id: int) -> None:
|
||||||
|
lock = coordinator.data[lock_id]
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
TedeeBinarySensorEntity(lock, coordinator, entity_description)
|
||||||
|
for entity_description in ENTITIES
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
coordinator.new_lock_callbacks.append(_async_add_new_lock)
|
||||||
|
|
||||||
|
|
||||||
class TedeeBinarySensorEntity(TedeeDescriptionEntity, BinarySensorEntity):
|
class TedeeBinarySensorEntity(TedeeDescriptionEntity, BinarySensorEntity):
|
||||||
"""Tedee sensor entity."""
|
"""Tedee sensor entity."""
|
||||||
|
|
|
@ -50,6 +50,8 @@ 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.new_lock_callbacks: list[Callable[[int], None]] = []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def bridge(self) -> TedeeBridge:
|
def bridge(self) -> TedeeBridge:
|
||||||
|
@ -82,6 +84,15 @@ 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._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:
|
||||||
|
|
|
@ -29,6 +29,15 @@ async def async_setup_entry(
|
||||||
else:
|
else:
|
||||||
entities.append(TedeeLockEntity(lock, coordinator))
|
entities.append(TedeeLockEntity(lock, coordinator))
|
||||||
|
|
||||||
|
def _async_add_new_lock(lock_id: int) -> None:
|
||||||
|
lock = coordinator.data[lock_id]
|
||||||
|
if lock.is_enabled_pullspring:
|
||||||
|
async_add_entities([TedeeLockWithLatchEntity(lock, coordinator)])
|
||||||
|
else:
|
||||||
|
async_add_entities([TedeeLockEntity(lock, coordinator)])
|
||||||
|
|
||||||
|
coordinator.new_lock_callbacks.append(_async_add_new_lock)
|
||||||
|
|
||||||
async_add_entities(entities)
|
async_add_entities(entities)
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -62,6 +62,17 @@ async def async_setup_entry(
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def _async_add_new_lock(lock_id: int) -> None:
|
||||||
|
lock = coordinator.data[lock_id]
|
||||||
|
async_add_entities(
|
||||||
|
[
|
||||||
|
TedeeSensorEntity(lock, coordinator, entity_description)
|
||||||
|
for entity_description in ENTITIES
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
coordinator.new_lock_callbacks.append(_async_add_new_lock)
|
||||||
|
|
||||||
|
|
||||||
class TedeeSensorEntity(TedeeDescriptionEntity, SensorEntity):
|
class TedeeSensorEntity(TedeeDescriptionEntity, SensorEntity):
|
||||||
"""Tedee sensor entity."""
|
"""Tedee sensor entity."""
|
||||||
|
|
|
@ -128,4 +128,4 @@
|
||||||
'last_updated': <ANY>,
|
'last_updated': <ANY>,
|
||||||
'state': 'off',
|
'state': 'off',
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
|
@ -26,4 +26,4 @@
|
||||||
'sw_version': None,
|
'sw_version': None,
|
||||||
'via_device_id': None,
|
'via_device_id': None,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
|
@ -1,13 +1,18 @@
|
||||||
"""Tests for the Tedee Binary Sensors."""
|
"""Tests for the Tedee Binary Sensors."""
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from pytedee_async import TedeeLock
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
pytestmark = pytest.mark.usefixtures("init_integration")
|
||||||
|
|
||||||
BINARY_SENSORS = (
|
BINARY_SENSORS = (
|
||||||
|
@ -32,3 +37,25 @@ async def test_binary_sensors(
|
||||||
entry = entity_registry.async_get(state.entity_id)
|
entry = entity_registry.async_get(state.entity_id)
|
||||||
assert entry
|
assert entry
|
||||||
assert entry == snapshot(name=f"entry-{key}")
|
assert entry == snapshot(name=f"entry-{key}")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_new_binary_sensors(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_tedee: MagicMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure binary sensors for new lock are added automatically."""
|
||||||
|
|
||||||
|
for key in BINARY_SENSORS:
|
||||||
|
state = hass.states.get(f"binary_sensor.lock_4e5f_{key}")
|
||||||
|
assert state is None
|
||||||
|
|
||||||
|
mock_tedee.locks_dict[666666] = TedeeLock("Lock-4E5F", 666666, 2)
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
for key in BINARY_SENSORS:
|
||||||
|
state = hass.states.get(f"binary_sensor.lock_4e5f_{key}")
|
||||||
|
assert state
|
||||||
|
|
|
@ -3,6 +3,7 @@ from datetime import timedelta
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
from freezegun.api import FrozenDateTimeFactory
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from pytedee_async import TedeeLock
|
||||||
from pytedee_async.exception import (
|
from pytedee_async.exception import (
|
||||||
TedeeClientException,
|
TedeeClientException,
|
||||||
TedeeDataUpdateException,
|
TedeeDataUpdateException,
|
||||||
|
@ -207,3 +208,31 @@ async def test_update_failed(
|
||||||
state = hass.states.get("lock.lock_1a2b")
|
state = hass.states.get("lock.lock_1a2b")
|
||||||
assert state is not None
|
assert state is not None
|
||||||
assert state.state == STATE_UNAVAILABLE
|
assert state.state == STATE_UNAVAILABLE
|
||||||
|
|
||||||
|
|
||||||
|
async def test_new_lock(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_tedee: MagicMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure new lock is added automatically."""
|
||||||
|
|
||||||
|
state = hass.states.get("lock.lock_4e5f")
|
||||||
|
assert state is None
|
||||||
|
|
||||||
|
mock_tedee.locks_dict[666666] = TedeeLock("Lock-4E5F", 666666, 2)
|
||||||
|
mock_tedee.locks_dict[777777] = TedeeLock(
|
||||||
|
"Lock-6G7H",
|
||||||
|
777777,
|
||||||
|
4,
|
||||||
|
is_enabled_pullspring=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
state = hass.states.get("lock.lock_4e5f")
|
||||||
|
assert state
|
||||||
|
state = hass.states.get("lock.lock_6g7h")
|
||||||
|
assert state
|
||||||
|
|
|
@ -1,14 +1,19 @@
|
||||||
"""Tests for the Tedee Sensors."""
|
"""Tests for the Tedee Sensors."""
|
||||||
|
|
||||||
|
|
||||||
|
from datetime import timedelta
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
|
|
||||||
|
from freezegun.api import FrozenDateTimeFactory
|
||||||
|
from pytedee_async import TedeeLock
|
||||||
import pytest
|
import pytest
|
||||||
from syrupy import SnapshotAssertion
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
from homeassistant.helpers import entity_registry as er
|
from homeassistant.helpers import entity_registry as er
|
||||||
|
|
||||||
|
from tests.common import async_fire_time_changed
|
||||||
|
|
||||||
pytestmark = pytest.mark.usefixtures("init_integration")
|
pytestmark = pytest.mark.usefixtures("init_integration")
|
||||||
|
|
||||||
|
|
||||||
|
@ -34,3 +39,25 @@ async def test_sensors(
|
||||||
assert entry
|
assert entry
|
||||||
assert entry.device_id
|
assert entry.device_id
|
||||||
assert entry == snapshot(name=f"entry-{key}")
|
assert entry == snapshot(name=f"entry-{key}")
|
||||||
|
|
||||||
|
|
||||||
|
async def test_new_sensors(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_tedee: MagicMock,
|
||||||
|
freezer: FrozenDateTimeFactory,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure sensors for new lock are added automatically."""
|
||||||
|
|
||||||
|
for key in SENSORS:
|
||||||
|
state = hass.states.get(f"sensor.lock_4e5f_{key}")
|
||||||
|
assert state is None
|
||||||
|
|
||||||
|
mock_tedee.locks_dict[666666] = TedeeLock("Lock-4E5F", 666666, 2)
|
||||||
|
|
||||||
|
freezer.tick(timedelta(minutes=10))
|
||||||
|
async_fire_time_changed(hass)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
for key in SENSORS:
|
||||||
|
state = hass.states.get(f"sensor.lock_4e5f_{key}")
|
||||||
|
assert state
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue