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
pull/44566/head
Allen Porter 2020-12-27 00:49:22 -08:00 committed by GitHub
parent 9531b08f2a
commit 51b88337ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 19 additions and 51 deletions

View File

@ -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()

View File

@ -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):

View File

@ -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

View File

@ -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",

View File

@ -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)
)

View File

@ -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

View File

@ -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

View File

@ -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={}):

View File

@ -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