Remove Knocki triggers on runtime (#120452)

* Bump Knocki to 0.2.0

* Remove triggers on runtime in Knocki

* Fix
This commit is contained in:
Joost Lekkerkerker 2024-06-26 09:52:05 +02:00 committed by GitHub
parent 82b8b73b8a
commit 59959141af
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 92 additions and 3 deletions

View File

@ -2,7 +2,7 @@
from __future__ import annotations from __future__ import annotations
from knocki import EventType, KnockiClient from knocki import Event, EventType, KnockiClient
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
@ -30,6 +30,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: KnockiConfigEntry) -> bo
client.register_listener(EventType.CREATED, coordinator.add_trigger) client.register_listener(EventType.CREATED, coordinator.add_trigger)
) )
async def _refresh_coordinator(_: Event) -> None:
await coordinator.async_refresh()
entry.async_on_unload(
client.register_listener(EventType.DELETED, _refresh_coordinator)
)
entry.runtime_data = coordinator 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

@ -2,7 +2,9 @@
from knocki import Event, KnockiClient, KnockiConnectionError, Trigger from knocki import Event, KnockiClient, KnockiConnectionError, Trigger
from homeassistant.components.event import DOMAIN as EVENT_DOMAIN
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, LOGGER from .const import DOMAIN, LOGGER
@ -19,12 +21,20 @@ class KnockiCoordinator(DataUpdateCoordinator[dict[int, Trigger]]):
name=DOMAIN, name=DOMAIN,
) )
self.client = client self.client = client
self._known_triggers: set[tuple[str, int]] = set()
async def _async_update_data(self) -> dict[int, Trigger]: async def _async_update_data(self) -> dict[int, Trigger]:
try: try:
triggers = await self.client.get_triggers() triggers = await self.client.get_triggers()
except KnockiConnectionError as exc: except KnockiConnectionError as exc:
raise UpdateFailed from exc raise UpdateFailed from exc
current_triggers = {
(trigger.device_id, trigger.details.trigger_id) for trigger in triggers
}
removed_triggers = self._known_triggers - current_triggers
for trigger in removed_triggers:
await self._delete_device(trigger)
self._known_triggers = current_triggers
return {trigger.details.trigger_id: trigger for trigger in triggers} return {trigger.details.trigger_id: trigger for trigger in triggers}
def add_trigger(self, event: Event) -> None: def add_trigger(self, event: Event) -> None:
@ -32,3 +42,16 @@ class KnockiCoordinator(DataUpdateCoordinator[dict[int, Trigger]]):
self.async_set_updated_data( self.async_set_updated_data(
{**self.data, event.payload.details.trigger_id: event.payload} {**self.data, event.payload.details.trigger_id: event.payload}
) )
self._known_triggers.add(
(event.payload.device_id, event.payload.details.trigger_id)
)
async def _delete_device(self, trigger: tuple[str, int]) -> None:
"""Delete a device from the coordinator."""
device_id, trigger_id = trigger
entity_registry = er.async_get(self.hass)
entity_entry = entity_registry.async_get_entity_id(
EVENT_DOMAIN, DOMAIN, f"{device_id}_{trigger_id}"
)
if entity_entry:
entity_registry.async_remove(entity_entry)

View File

@ -0,0 +1,30 @@
[
{
"device": "KNC1-W-00000214",
"gesture": "d060b870-15ba-42c9-a932-2d2951087152",
"details": {
"description": "Eeee",
"name": "Aaaa",
"id": 31
},
"type": "homeassistant",
"user": "7a4d5bf9-01b1-413b-bb4d-77728e931dcc",
"updatedAt": 1716378013721,
"createdAt": 1716378013721,
"id": "1a050b25-7fed-4e0e-b5af-792b8b4650de"
},
{
"device": "KNC1-W-00000214",
"gesture": "d060b870-15ba-42c9-a932-2d2951087152",
"details": {
"description": "Eeee",
"name": "Bbbb",
"id": 32
},
"type": "homeassistant",
"user": "7a4d5bf9-01b1-413b-bb4d-77728e931dcc",
"updatedAt": 1716378013721,
"createdAt": 1716378013721,
"id": "1a050b25-7fed-4e0e-b5af-792b8b4650de"
}
]

View File

@ -1,6 +1,6 @@
"""Tests for the Knocki event platform.""" """Tests for the Knocki event platform."""
from collections.abc import Callable from collections.abc import Awaitable, Callable
from unittest.mock import AsyncMock from unittest.mock import AsyncMock
from knocki import Event, EventType, Trigger, TriggerDetails from knocki import Event, EventType, Trigger, TriggerDetails
@ -89,10 +89,39 @@ async def test_adding_runtime_entities(
assert not hass.states.get("event.knc1_w_00000214_aaaa") assert not hass.states.get("event.knc1_w_00000214_aaaa")
add_trigger_function: Callable[[Event], None] = ( add_trigger_function: Callable[[Event], None] = (
mock_knocki_client.register_listener.call_args[0][1] mock_knocki_client.register_listener.call_args_list[0][0][1]
) )
trigger = Trigger.from_dict(load_json_array_fixture("triggers.json", DOMAIN)[0]) trigger = Trigger.from_dict(load_json_array_fixture("triggers.json", DOMAIN)[0])
add_trigger_function(Event(EventType.CREATED, trigger)) add_trigger_function(Event(EventType.CREATED, trigger))
assert hass.states.get("event.knc1_w_00000214_aaaa") is not None assert hass.states.get("event.knc1_w_00000214_aaaa") is not None
async def test_removing_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 = [
Trigger.from_dict(trigger)
for trigger in load_json_array_fixture("more_triggers.json", DOMAIN)
]
await setup_integration(hass, mock_config_entry)
assert hass.states.get("event.knc1_w_00000214_aaaa") is not None
assert hass.states.get("event.knc1_w_00000214_bbbb") is not None
remove_trigger_function: Callable[[Event], Awaitable[None]] = (
mock_knocki_client.register_listener.call_args_list[1][0][1]
)
trigger = Trigger.from_dict(load_json_array_fixture("triggers.json", DOMAIN)[0])
mock_knocki_client.get_triggers.return_value = [trigger]
await remove_trigger_function(Event(EventType.DELETED, trigger))
assert hass.states.get("event.knc1_w_00000214_aaaa") is not None
assert hass.states.get("event.knc1_w_00000214_bbbb") is None