From 51b88337caec91f86a6a6c9ace40fba62e271f56 Mon Sep 17 00:00:00 2001 From: Allen Porter Date: Sun, 27 Dec 2020 00:49:22 -0800 Subject: [PATCH] Simplify nest event handling (#44367) * Simplify nest event handling Use device specific update callbacks rather than a global callback. The motivation is to prepare for a follow up change that will store camera specific event tokens on the camera itself, so that a service can later fetch event specific image snapshots, which would be difficult to send across the event bus. * Increase nest camera test coverage * Remove unnecessary device updates for nest cameras * Remove unused imports * Fix device id check to look at returned entry * Remove unused imports after rebase * Partial revert of nest event simplification * Push more update logic into the nest library * Revert nest device_info changes * Revert test changes to restore global update behavior * Bump nest library version to support new callback interfaces --- homeassistant/components/nest/__init__.py | 19 ++++--------------- homeassistant/components/nest/camera_sdm.py | 10 ++-------- homeassistant/components/nest/climate_sdm.py | 12 ++---------- homeassistant/components/nest/manifest.json | 2 +- homeassistant/components/nest/sensor_sdm.py | 12 ++---------- requirements_all.txt | 2 +- requirements_test_all.txt | 2 +- tests/components/nest/common.py | 8 ++++---- tests/components/nest/test_device_trigger.py | 3 ++- 9 files changed, 19 insertions(+), 51 deletions(-) diff --git a/homeassistant/components/nest/__init__.py b/homeassistant/components/nest/__init__.py index e9bffa2706c..ab235d7559a 100644 --- a/homeassistant/components/nest/__init__.py +++ b/homeassistant/components/nest/__init__.py @@ -3,7 +3,7 @@ import asyncio import logging -from google_nest_sdm.event import AsyncEventCallback, EventMessage +from google_nest_sdm.event import EventMessage from google_nest_sdm.exceptions import AuthException, GoogleNestException from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber import voluptuous as vol @@ -24,7 +24,6 @@ from homeassistant.helpers import ( config_entry_oauth2_flow, config_validation as cv, ) -from homeassistant.helpers.dispatcher import async_dispatcher_send from . import api, config_flow from .const import ( @@ -34,7 +33,6 @@ from .const import ( DOMAIN, OAUTH2_AUTHORIZE, OAUTH2_TOKEN, - SIGNAL_NEST_UPDATE, ) from .events import EVENT_NAME_MAP, NEST_EVENT from .legacy import async_setup_legacy, async_setup_legacy_entry @@ -106,7 +104,7 @@ async def async_setup(hass: HomeAssistant, config: dict): return True -class SignalUpdateCallback(AsyncEventCallback): +class SignalUpdateCallback: """An EventCallback invoked when new events arrive from subscriber.""" def __init__(self, hass: HomeAssistant): @@ -116,17 +114,8 @@ class SignalUpdateCallback(AsyncEventCallback): async def async_handle_event(self, event_message: EventMessage): """Process an incoming EventMessage.""" if not event_message.resource_update_name: - _LOGGER.debug("Ignoring event with no device_id") return device_id = event_message.resource_update_name - _LOGGER.debug("Update for %s @ %s", device_id, event_message.timestamp) - traits = event_message.resource_update_traits - if traits: - _LOGGER.debug("Trait update %s", traits.keys()) - # This event triggered an update to a device that changed some - # properties which the DeviceManager should already have received. - # Send a signal to refresh state of all listening devices. - async_dispatcher_send(self._hass, SIGNAL_NEST_UPDATE) events = event_message.resource_update_events if not events: return @@ -134,7 +123,6 @@ class SignalUpdateCallback(AsyncEventCallback): device_registry = await self._hass.helpers.device_registry.async_get_registry() device_entry = device_registry.async_get_device({(DOMAIN, device_id)}, ()) if not device_entry: - _LOGGER.debug("Ignoring event for unregistered device '%s'", device_id) return for event in events: event_type = EVENT_NAME_MAP.get(event) @@ -170,7 +158,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry): subscriber = GoogleNestSubscriber( auth, config[CONF_PROJECT_ID], config[CONF_SUBSCRIBER_ID] ) - subscriber.set_update_callback(SignalUpdateCallback(hass)) + callback = SignalUpdateCallback(hass) + subscriber.set_update_callback(callback.async_handle_event) try: await subscriber.start_async() diff --git a/homeassistant/components/nest/camera_sdm.py b/homeassistant/components/nest/camera_sdm.py index 37bd2fed8a6..a643de0e6c9 100644 --- a/homeassistant/components/nest/camera_sdm.py +++ b/homeassistant/components/nest/camera_sdm.py @@ -13,12 +13,11 @@ from homeassistant.components.camera import SUPPORT_STREAM, Camera from homeassistant.components.ffmpeg import async_get_image from homeassistant.config_entries import ConfigEntry from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.event import async_track_point_in_utc_time from homeassistant.helpers.typing import HomeAssistantType from homeassistant.util.dt import utcnow -from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE +from .const import DATA_SUBSCRIBER, DOMAIN from .device_info import DeviceInfo _LOGGER = logging.getLogger(__name__) @@ -151,13 +150,8 @@ class NestCamera(Camera): async def async_added_to_hass(self): """Run when entity is added to register update signal handler.""" - # Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted - # here to re-fresh the signals from _device. Unregister this callback - # when the entity is removed. self.async_on_remove( - async_dispatcher_connect( - self.hass, SIGNAL_NEST_UPDATE, self.async_write_ha_state - ) + self._device.add_update_listener(self.async_write_ha_state) ) async def async_camera_image(self): diff --git a/homeassistant/components/nest/climate_sdm.py b/homeassistant/components/nest/climate_sdm.py index 368eb8b3465..08cb0161bd9 100644 --- a/homeassistant/components/nest/climate_sdm.py +++ b/homeassistant/components/nest/climate_sdm.py @@ -36,10 +36,9 @@ from homeassistant.components.climate.const import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import ATTR_TEMPERATURE, TEMP_CELSIUS from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.typing import HomeAssistantType -from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE +from .const import DATA_SUBSCRIBER, DOMAIN from .device_info import DeviceInfo # Mapping for sdm.devices.traits.ThermostatMode mode field @@ -126,16 +125,9 @@ class ThermostatEntity(ClimateEntity): async def async_added_to_hass(self): """Run when entity is added to register update signal handler.""" - # Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted - # here to re-fresh the signals from _device. Unregister this callback - # when the entity is removed. self._supported_features = self._get_supported_features() self.async_on_remove( - async_dispatcher_connect( - self.hass, - SIGNAL_NEST_UPDATE, - self.async_write_ha_state, - ) + self._device.add_update_listener(self.async_write_ha_state) ) @property diff --git a/homeassistant/components/nest/manifest.json b/homeassistant/components/nest/manifest.json index 028f56587a1..7d60bb1cf5d 100644 --- a/homeassistant/components/nest/manifest.json +++ b/homeassistant/components/nest/manifest.json @@ -6,7 +6,7 @@ "documentation": "https://www.home-assistant.io/integrations/nest", "requirements": [ "python-nest==4.1.0", - "google-nest-sdm==0.2.1" + "google-nest-sdm==0.2.5" ], "codeowners": [ "@awarecan", diff --git a/homeassistant/components/nest/sensor_sdm.py b/homeassistant/components/nest/sensor_sdm.py index 9009414c5b4..52490f41f86 100644 --- a/homeassistant/components/nest/sensor_sdm.py +++ b/homeassistant/components/nest/sensor_sdm.py @@ -15,11 +15,10 @@ from homeassistant.const import ( TEMP_CELSIUS, ) from homeassistant.exceptions import PlatformNotReady -from homeassistant.helpers.dispatcher import async_dispatcher_connect from homeassistant.helpers.entity import Entity from homeassistant.helpers.typing import HomeAssistantType -from .const import DATA_SUBSCRIBER, DOMAIN, SIGNAL_NEST_UPDATE +from .const import DATA_SUBSCRIBER, DOMAIN from .device_info import DeviceInfo _LOGGER = logging.getLogger(__name__) @@ -80,15 +79,8 @@ class SensorBase(Entity): async def async_added_to_hass(self): """Run when entity is added to register update signal handler.""" - # Event messages trigger the SIGNAL_NEST_UPDATE, which is intercepted - # here to re-fresh the signals from _device. Unregister this callback - # when the entity is removed. self.async_on_remove( - async_dispatcher_connect( - self.hass, - SIGNAL_NEST_UPDATE, - self.async_write_ha_state, - ) + self._device.add_update_listener(self.async_write_ha_state) ) diff --git a/requirements_all.txt b/requirements_all.txt index ec7b256775c..917006cea97 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -681,7 +681,7 @@ google-cloud-pubsub==2.1.0 google-cloud-texttospeech==0.4.0 # homeassistant.components.nest -google-nest-sdm==0.2.1 +google-nest-sdm==0.2.5 # homeassistant.components.google_travel_time googlemaps==2.5.1 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index 6fc1b3da392..485caf8bfce 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -352,7 +352,7 @@ google-api-python-client==1.6.4 google-cloud-pubsub==2.1.0 # homeassistant.components.nest -google-nest-sdm==0.2.1 +google-nest-sdm==0.2.5 # homeassistant.components.gree greeclimate==0.10.3 diff --git a/tests/components/nest/common.py b/tests/components/nest/common.py index cd3a06a5afa..d7b78b98f8f 100644 --- a/tests/components/nest/common.py +++ b/tests/components/nest/common.py @@ -1,9 +1,10 @@ """Common libraries for test setup.""" import time +from typing import Awaitable, Callable from google_nest_sdm.device_manager import DeviceManager -from google_nest_sdm.event import AsyncEventCallback, EventMessage +from google_nest_sdm.event import EventMessage from google_nest_sdm.google_nest_subscriber import GoogleNestSubscriber from homeassistant.components.nest import DOMAIN @@ -59,9 +60,8 @@ class FakeSubscriber(GoogleNestSubscriber): def __init__(self, device_manager: FakeDeviceManager): """Initialize Fake Subscriber.""" self._device_manager = device_manager - self._callback = None - def set_update_callback(self, callback: AsyncEventCallback): + def set_update_callback(self, callback: Callable[[EventMessage], Awaitable[None]]): """Capture the callback set by Home Assistant.""" self._callback = callback @@ -81,7 +81,7 @@ class FakeSubscriber(GoogleNestSubscriber): """Simulate a received pubsub message, invoked by tests.""" # Update device state, then invoke HomeAssistant to refresh await self._device_manager.async_handle_event(event_message) - await self._callback.async_handle_event(event_message) + await self._callback(event_message) async def async_setup_sdm_platform(hass, platform, devices={}, structures={}): diff --git a/tests/components/nest/test_device_trigger.py b/tests/components/nest/test_device_trigger.py index 3199b89f21c..29091f32f51 100644 --- a/tests/components/nest/test_device_trigger.py +++ b/tests/components/nest/test_device_trigger.py @@ -7,7 +7,8 @@ import homeassistant.components.automation as automation from homeassistant.components.device_automation.exceptions import ( InvalidDeviceAutomationConfig, ) -from homeassistant.components.nest import DOMAIN, NEST_EVENT +from homeassistant.components.nest import DOMAIN +from homeassistant.components.nest.events import NEST_EVENT from homeassistant.setup import async_setup_component from .common import async_setup_sdm_platform