Add coordinator to Knocki (#120251)

This commit is contained in:
Joost Lekkerkerker 2024-06-24 11:41:33 +02:00 committed by GitHub
parent c04a6cc639
commit f3a1ca6d54
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 89 additions and 22 deletions

View File

@ -2,27 +2,18 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from knocki import EventType, KnockiClient
from knocki import KnockiClient, KnockiConnectionError, Trigger
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_TOKEN, Platform from homeassistant.const import CONF_TOKEN, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .coordinator import KnockiCoordinator
PLATFORMS: list[Platform] = [Platform.EVENT] PLATFORMS: list[Platform] = [Platform.EVENT]
type KnockiConfigEntry = ConfigEntry[KnockiData] type KnockiConfigEntry = ConfigEntry[KnockiCoordinator]
@dataclass
class KnockiData:
"""Knocki data."""
client: KnockiClient
triggers: list[Trigger]
async def async_setup_entry(hass: HomeAssistant, entry: KnockiConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: KnockiConfigEntry) -> bool:
@ -31,12 +22,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: KnockiConfigEntry) -> bo
session=async_get_clientsession(hass), token=entry.data[CONF_TOKEN] session=async_get_clientsession(hass), token=entry.data[CONF_TOKEN]
) )
try: coordinator = KnockiCoordinator(hass, client)
triggers = await client.get_triggers()
except KnockiConnectionError as exc:
raise ConfigEntryNotReady from exc
entry.runtime_data = KnockiData(client=client, triggers=triggers) await coordinator.async_config_entry_first_refresh()
entry.async_on_unload(
client.register_listener(EventType.CREATED, coordinator.add_trigger)
)
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)

View File

@ -0,0 +1,34 @@
"""Update coordinator for Knocki integration."""
from knocki import Event, KnockiClient, KnockiConnectionError, Trigger
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, LOGGER
class KnockiCoordinator(DataUpdateCoordinator[dict[int, Trigger]]):
"""The Knocki coordinator."""
def __init__(self, hass: HomeAssistant, client: KnockiClient) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
)
self.client = client
async def _async_update_data(self) -> dict[int, Trigger]:
try:
triggers = await self.client.get_triggers()
except KnockiConnectionError as exc:
raise UpdateFailed from exc
return {trigger.details.trigger_id: trigger for trigger in triggers}
def add_trigger(self, event: Event) -> None:
"""Add a trigger to the coordinator."""
self.async_set_updated_data(
{**self.data, event.payload.details.trigger_id: event.payload}
)

View File

@ -3,7 +3,7 @@
from knocki import Event, EventType, KnockiClient, Trigger from knocki import Event, EventType, KnockiClient, Trigger
from homeassistant.components.event import EventEntity from homeassistant.components.event import EventEntity
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -17,10 +17,26 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback, async_add_entities: AddEntitiesCallback,
) -> None: ) -> None:
"""Set up Knocki from a config entry.""" """Set up Knocki from a config entry."""
entry_data = entry.runtime_data coordinator = entry.runtime_data
added_triggers = set(coordinator.data)
@callback
def _async_add_entities() -> None:
current_triggers = set(coordinator.data)
new_triggers = current_triggers - added_triggers
added_triggers.update(new_triggers)
if new_triggers:
async_add_entities(
KnockiTrigger(coordinator.data[trigger], coordinator.client)
for trigger in new_triggers
)
coordinator.async_add_listener(_async_add_entities)
async_add_entities( async_add_entities(
KnockiTrigger(trigger, entry_data.client) for trigger in entry_data.triggers KnockiTrigger(trigger, coordinator.client)
for trigger in coordinator.data.values()
) )

View File

@ -7,13 +7,14 @@ from knocki import Event, EventType, Trigger, TriggerDetails
import pytest import pytest
from syrupy import SnapshotAssertion from syrupy import SnapshotAssertion
from homeassistant.components.knocki.const import DOMAIN
from homeassistant.const import STATE_UNKNOWN from homeassistant.const import STATE_UNKNOWN
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 . import setup_integration from . import setup_integration
from tests.common import MockConfigEntry, snapshot_platform from tests.common import MockConfigEntry, load_json_array_fixture, snapshot_platform
async def test_entities( async def test_entities(
@ -73,3 +74,25 @@ async def test_subscription(
await hass.async_block_till_done() await hass.async_block_till_done()
assert mock_knocki_client.register_listener.return_value.called assert mock_knocki_client.register_listener.return_value.called
async def test_adding_runtime_entities(
hass: HomeAssistant,
mock_knocki_client: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test we can create devices on runtime."""
mock_knocki_client.get_triggers.return_value = []
await setup_integration(hass, mock_config_entry)
assert not hass.states.get("event.knc1_w_00000214_aaaa")
add_trigger_function: Callable[[Event], None] = (
mock_knocki_client.register_listener.call_args[0][1]
)
trigger = Trigger.from_dict(load_json_array_fixture("triggers.json", DOMAIN)[0])
add_trigger_function(Event(EventType.CREATED, trigger))
assert hass.states.get("event.knc1_w_00000214_aaaa") is not None