mirror of
https://github.com/home-assistant/core.git
synced 2025-07-20 19:57:07 +00:00
Add tedee bridge as via_device for tedee integration (#106914)
* add bridge as via_device * add bridge as via_device * move getting bridge to update_data * add bridge property
This commit is contained in:
parent
5003993658
commit
87c79ef57f
@ -4,6 +4,7 @@ import logging
|
|||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import Platform
|
from homeassistant.const import Platform
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from .const import DOMAIN
|
from .const import DOMAIN
|
||||||
from .coordinator import TedeeApiCoordinator
|
from .coordinator import TedeeApiCoordinator
|
||||||
@ -24,6 +25,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
await coordinator.async_config_entry_first_refresh()
|
||||||
|
|
||||||
|
device_registry = dr.async_get(hass)
|
||||||
|
device_registry.async_get_or_create(
|
||||||
|
config_entry_id=entry.entry_id,
|
||||||
|
identifiers={(DOMAIN, coordinator.bridge.serial)},
|
||||||
|
manufacturer="Tedee",
|
||||||
|
name=coordinator.bridge.name,
|
||||||
|
model="Bridge",
|
||||||
|
serial_number=coordinator.bridge.serial,
|
||||||
|
)
|
||||||
|
|
||||||
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
|
||||||
|
|
||||||
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
@ -11,6 +11,7 @@ from pytedee_async import (
|
|||||||
TedeeLocalAuthException,
|
TedeeLocalAuthException,
|
||||||
TedeeLock,
|
TedeeLock,
|
||||||
)
|
)
|
||||||
|
from pytedee_async.bridge import TedeeBridge
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntry
|
from homeassistant.config_entries import ConfigEntry
|
||||||
from homeassistant.const import CONF_HOST
|
from homeassistant.const import CONF_HOST
|
||||||
@ -40,6 +41,7 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
|
|||||||
update_interval=SCAN_INTERVAL,
|
update_interval=SCAN_INTERVAL,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self._bridge: TedeeBridge | None = None
|
||||||
self.tedee_client = TedeeClient(
|
self.tedee_client = TedeeClient(
|
||||||
local_token=self.config_entry.data[CONF_LOCAL_ACCESS_TOKEN],
|
local_token=self.config_entry.data[CONF_LOCAL_ACCESS_TOKEN],
|
||||||
local_ip=self.config_entry.data[CONF_HOST],
|
local_ip=self.config_entry.data[CONF_HOST],
|
||||||
@ -47,18 +49,31 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
|
|||||||
|
|
||||||
self._next_get_locks = time.time()
|
self._next_get_locks = time.time()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bridge(self) -> TedeeBridge:
|
||||||
|
"""Return bridge."""
|
||||||
|
assert self._bridge
|
||||||
|
return self._bridge
|
||||||
|
|
||||||
async def _async_update_data(self) -> dict[int, TedeeLock]:
|
async def _async_update_data(self) -> dict[int, TedeeLock]:
|
||||||
"""Fetch data from API endpoint."""
|
"""Fetch data from API endpoint."""
|
||||||
|
if self._bridge is None:
|
||||||
|
|
||||||
|
async def _async_get_bridge() -> None:
|
||||||
|
self._bridge = await self.tedee_client.get_local_bridge()
|
||||||
|
|
||||||
|
_LOGGER.debug("Update coordinator: Getting bridge from API")
|
||||||
|
await self._async_update(_async_get_bridge)
|
||||||
|
|
||||||
_LOGGER.debug("Update coordinator: Getting locks from API")
|
_LOGGER.debug("Update coordinator: Getting locks from API")
|
||||||
# once every hours get all lock details, otherwise use the sync endpoint
|
# once every hours get all lock details, otherwise use the sync endpoint
|
||||||
if self._next_get_locks <= time.time():
|
if self._next_get_locks <= time.time():
|
||||||
_LOGGER.debug("Updating through /my/lock endpoint")
|
_LOGGER.debug("Updating through /my/lock endpoint")
|
||||||
await self._async_update_locks(self.tedee_client.get_locks)
|
await self._async_update(self.tedee_client.get_locks)
|
||||||
self._next_get_locks = time.time() + GET_LOCKS_INTERVAL_SECONDS
|
self._next_get_locks = time.time() + GET_LOCKS_INTERVAL_SECONDS
|
||||||
else:
|
else:
|
||||||
_LOGGER.debug("Updating through /sync endpoint")
|
_LOGGER.debug("Updating through /sync endpoint")
|
||||||
await self._async_update_locks(self.tedee_client.sync)
|
await self._async_update(self.tedee_client.sync)
|
||||||
|
|
||||||
_LOGGER.debug(
|
_LOGGER.debug(
|
||||||
"available_locks: %s",
|
"available_locks: %s",
|
||||||
@ -67,9 +82,7 @@ class TedeeApiCoordinator(DataUpdateCoordinator[dict[int, TedeeLock]]):
|
|||||||
|
|
||||||
return self.tedee_client.locks_dict
|
return self.tedee_client.locks_dict
|
||||||
|
|
||||||
async def _async_update_locks(
|
async def _async_update(self, update_fn: Callable[[], Awaitable[None]]) -> None:
|
||||||
self, update_fn: Callable[[], Awaitable[None]]
|
|
||||||
) -> None:
|
|
||||||
"""Update locks based on update function."""
|
"""Update locks based on update function."""
|
||||||
try:
|
try:
|
||||||
await update_fn()
|
await update_fn()
|
||||||
|
@ -32,6 +32,7 @@ class TedeeEntity(CoordinatorEntity[TedeeApiCoordinator]):
|
|||||||
name=lock.lock_name,
|
name=lock.lock_name,
|
||||||
manufacturer="Tedee",
|
manufacturer="Tedee",
|
||||||
model=lock.lock_type,
|
model=lock.lock_type,
|
||||||
|
via_device=(DOMAIN, coordinator.bridge.serial),
|
||||||
)
|
)
|
||||||
|
|
||||||
@callback
|
@callback
|
||||||
|
29
tests/components/tedee/snapshots/test_init.ambr
Normal file
29
tests/components/tedee/snapshots/test_init.ambr
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
# serializer version: 1
|
||||||
|
# name: test_bridge_device
|
||||||
|
DeviceRegistryEntrySnapshot({
|
||||||
|
'area_id': None,
|
||||||
|
'config_entries': <ANY>,
|
||||||
|
'configuration_url': None,
|
||||||
|
'connections': set({
|
||||||
|
}),
|
||||||
|
'disabled_by': None,
|
||||||
|
'entry_type': None,
|
||||||
|
'hw_version': None,
|
||||||
|
'id': <ANY>,
|
||||||
|
'identifiers': set({
|
||||||
|
tuple(
|
||||||
|
'tedee',
|
||||||
|
'0000-0000',
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
'is_new': False,
|
||||||
|
'manufacturer': 'Tedee',
|
||||||
|
'model': 'Bridge',
|
||||||
|
'name': 'Bridge-AB1C',
|
||||||
|
'name_by_user': None,
|
||||||
|
'serial_number': '0000-0000',
|
||||||
|
'suggested_area': None,
|
||||||
|
'sw_version': None,
|
||||||
|
'via_device_id': None,
|
||||||
|
})
|
||||||
|
# ---
|
@ -68,7 +68,7 @@
|
|||||||
'serial_number': None,
|
'serial_number': None,
|
||||||
'suggested_area': None,
|
'suggested_area': None,
|
||||||
'sw_version': None,
|
'sw_version': None,
|
||||||
'via_device_id': None,
|
'via_device_id': <ANY>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
# name: test_lock_without_pullspring
|
# name: test_lock_without_pullspring
|
||||||
@ -140,6 +140,6 @@
|
|||||||
'serial_number': None,
|
'serial_number': None,
|
||||||
'suggested_area': None,
|
'suggested_area': None,
|
||||||
'sw_version': None,
|
'sw_version': None,
|
||||||
'via_device_id': None,
|
'via_device_id': <ANY>,
|
||||||
})
|
})
|
||||||
# ---
|
# ---
|
||||||
|
@ -3,9 +3,11 @@ from unittest.mock import MagicMock
|
|||||||
|
|
||||||
from pytedee_async.exception import TedeeAuthException, TedeeClientException
|
from pytedee_async.exception import TedeeAuthException, TedeeClientException
|
||||||
import pytest
|
import pytest
|
||||||
|
from syrupy import SnapshotAssertion
|
||||||
|
|
||||||
from homeassistant.config_entries import ConfigEntryState
|
from homeassistant.config_entries import ConfigEntryState
|
||||||
from homeassistant.core import HomeAssistant
|
from homeassistant.core import HomeAssistant
|
||||||
|
from homeassistant.helpers import device_registry as dr
|
||||||
|
|
||||||
from tests.common import MockConfigEntry
|
from tests.common import MockConfigEntry
|
||||||
|
|
||||||
@ -37,7 +39,7 @@ async def test_config_entry_not_ready(
|
|||||||
mock_tedee: MagicMock,
|
mock_tedee: MagicMock,
|
||||||
side_effect: Exception,
|
side_effect: Exception,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Test the LaMetric configuration entry not ready."""
|
"""Test the Tedee configuration entry not ready."""
|
||||||
mock_tedee.get_locks.side_effect = side_effect
|
mock_tedee.get_locks.side_effect = side_effect
|
||||||
|
|
||||||
mock_config_entry.add_to_hass(hass)
|
mock_config_entry.add_to_hass(hass)
|
||||||
@ -46,3 +48,22 @@ async def test_config_entry_not_ready(
|
|||||||
|
|
||||||
assert len(mock_tedee.get_locks.mock_calls) == 1
|
assert len(mock_tedee.get_locks.mock_calls) == 1
|
||||||
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
assert mock_config_entry.state is ConfigEntryState.SETUP_RETRY
|
||||||
|
|
||||||
|
|
||||||
|
async def test_bridge_device(
|
||||||
|
hass: HomeAssistant,
|
||||||
|
mock_config_entry: MockConfigEntry,
|
||||||
|
mock_tedee: MagicMock,
|
||||||
|
device_registry: dr.DeviceRegistry,
|
||||||
|
snapshot: SnapshotAssertion,
|
||||||
|
) -> None:
|
||||||
|
"""Ensure the bridge device is registered."""
|
||||||
|
mock_config_entry.add_to_hass(hass)
|
||||||
|
await hass.config_entries.async_setup(mock_config_entry.entry_id)
|
||||||
|
await hass.async_block_till_done()
|
||||||
|
|
||||||
|
device = device_registry.async_get_device(
|
||||||
|
{(mock_config_entry.domain, mock_tedee.get_local_bridge.return_value.serial)}
|
||||||
|
)
|
||||||
|
assert device
|
||||||
|
assert device == snapshot
|
||||||
|
Loading…
x
Reference in New Issue
Block a user