Skip to content

Commit

Permalink
Simplify nest event handling (home-assistant#44367)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
allenporter committed Dec 27, 2020
1 parent 9531b08 commit 51b8833
Show file tree
Hide file tree
Showing 9 changed files with 19 additions and 51 deletions.
19 changes: 4 additions & 15 deletions homeassistant/components/nest/__init__.py
Expand Up @@ -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
Expand All @@ -24,7 +24,6 @@
config_entry_oauth2_flow,
config_validation as cv,
)
from homeassistant.helpers.dispatcher import async_dispatcher_send

from . import api, config_flow
from .const import (
Expand All @@ -34,7 +33,6 @@
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
Expand Down Expand Up @@ -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):
Expand All @@ -116,25 +114,15 @@ def __init__(self, hass: HomeAssistant):
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
_LOGGER.debug("Event Update %s", events.keys())
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)
Expand Down Expand Up @@ -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()
Expand Down
10 changes: 2 additions & 8 deletions homeassistant/components/nest/camera_sdm.py
Expand Up @@ -13,12 +13,11 @@
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__)
Expand Down Expand Up @@ -151,13 +150,8 @@ async def async_will_remove_from_hass(self):

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):
Expand Down
12 changes: 2 additions & 10 deletions homeassistant/components/nest/climate_sdm.py
Expand Up @@ -36,10 +36,9 @@
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
Expand Down Expand Up @@ -126,16 +125,9 @@ def device_info(self):

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
Expand Down
2 changes: 1 addition & 1 deletion homeassistant/components/nest/manifest.json
Expand Up @@ -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",
Expand Down
12 changes: 2 additions & 10 deletions homeassistant/components/nest/sensor_sdm.py
Expand Up @@ -15,11 +15,10 @@
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__)
Expand Down Expand Up @@ -80,15 +79,8 @@ def device_info(self):

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


Expand Down
2 changes: 1 addition & 1 deletion requirements_all.txt
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion requirements_test_all.txt
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions 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
Expand Down Expand Up @@ -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

Expand All @@ -81,7 +81,7 @@ async def async_receive_event(self, event_message: EventMessage):
"""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={}):
Expand Down
3 changes: 2 additions & 1 deletion tests/components/nest/test_device_trigger.py
Expand Up @@ -7,7 +7,8 @@
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
Expand Down

0 comments on commit 51b8833

Please sign in to comment.