From c62bfcaa4cc70571acca65875ffc8da8a54649f5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20Cea=20Fontenla?= Date: Tue, 28 Jun 2022 01:47:55 +0200 Subject: [PATCH] Nuki opener event on ring (#72793) * feat(nuki): add ring action timestamp attribute * feat(nuki): add ring action state attribute * Emit event on Nuki Opener ring * Removed event attributes * Use entity registry to get entity id * Move event firing to the async update method * Move events code outside try-except * Black autoformat * Added missing period to doc * Import order Co-authored-by: Franck Nijhof --- homeassistant/components/nuki/__init__.py | 41 +++++++++++++++++++++-- 1 file changed, 38 insertions(+), 3 deletions(-) diff --git a/homeassistant/components/nuki/__init__.py b/homeassistant/components/nuki/__init__.py index e9cef7aa6cd..a59b0a62f70 100644 --- a/homeassistant/components/nuki/__init__.py +++ b/homeassistant/components/nuki/__init__.py @@ -1,4 +1,5 @@ """The nuki component.""" +from collections import defaultdict from datetime import timedelta import logging @@ -12,6 +13,7 @@ from homeassistant import exceptions from homeassistant.config_entries import ConfigEntry from homeassistant.const import CONF_HOST, CONF_PORT, CONF_TOKEN, Platform from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry as er from homeassistant.helpers.update_coordinator import ( CoordinatorEntity, DataUpdateCoordinator, @@ -39,17 +41,36 @@ def _get_bridge_devices(bridge: NukiBridge) -> tuple[list[NukiLock], list[NukiOp return bridge.locks, bridge.openers -def _update_devices(devices: list[NukiDevice]) -> None: +def _update_devices(devices: list[NukiDevice]) -> dict[str, set[str]]: + """ + Update the Nuki devices. + + Returns: + A dict with the events to be fired. The event type is the key and the device ids are the value + """ + + events: dict[str, set[str]] = defaultdict(set) + for device in devices: for level in (False, True): try: - device.update(level) + if isinstance(device, NukiOpener): + last_ring_action_state = device.ring_action_state + + device.update(level) + + if not last_ring_action_state and device.ring_action_state: + events["ring"].add(device.nuki_id) + else: + device.update(level) except RequestException: continue if device.state not in ERROR_STATES: break + return events + async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: """Set up the Nuki entry.""" @@ -86,12 +107,26 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: # Note: asyncio.TimeoutError and aiohttp.ClientError are already # handled by the data update coordinator. async with async_timeout.timeout(10): - await hass.async_add_executor_job(_update_devices, locks + openers) + events = await hass.async_add_executor_job( + _update_devices, locks + openers + ) except InvalidCredentialsException as err: raise UpdateFailed(f"Invalid credentials for Bridge: {err}") from err except RequestException as err: raise UpdateFailed(f"Error communicating with Bridge: {err}") from err + ent_reg = er.async_get(hass) + for event, device_ids in events.items(): + for device_id in device_ids: + entity_id = ent_reg.async_get_entity_id( + Platform.LOCK, DOMAIN, device_id + ) + event_data = { + "entity_id": entity_id, + "type": event, + } + hass.bus.async_fire("nuki_event", event_data) + coordinator = DataUpdateCoordinator( hass, _LOGGER,